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 的时钟频率)(inputclk,inputrst_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 regdri_clk ,output reg [7 : 0]i2c_data_r,output regi2c_ack ,output regi2c_done,// -------------------------------------------------- //output regscl ,inout sda);// --------------------------------------------------------////next isdefine //// --------------------------------------------------------//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线上wiresda_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 maincode //// -------------------------------------------------------//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写数据需要添加间隔时间,读数据则不需要parameterWR_WAIT_TIME = 14'd5000, //写入间隔时间parameterMAX_BYTE = 16'd256//读写测试的字节个数) (inputclk ,inputrst_n ,// from I2C-control input [7 : 0]i2c_data_r,// 读出来的数据inputi2c_ack ,// 应答inputi2c_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 ; endelsebegin i2c_exec <= 0 ; // 把 i2c_ecec 看成是一个脉冲信号case(flow_cnt) 2'd0 : beginif(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;endelse beginflow_cnt <= flow_cnt + 2'b1;i2c_exec <= 1'b1;endendelse 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~255endelse 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;endelse beginflow_cnt <= 2'd2;i2c_addr <= i2c_addr + 16'b1;endend enddefault : ;endcaseendendendmodule
led.v
module LED #(parameter L_TIME = 17'd125_000)(inputclk ,//时钟信号inputrst_n ,//复位信号 inputrw_done ,//错误标志inputrw_result ,//E2PROM读写测试完成outputregled//E2PROM读写测试结果 0:失败 1:成功);//reg defineregrw_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灯熄灭endendendmodule
top.v
module IIC_top#(parameterSLAVE_ADDR = 7'b1010000 , //器件地址(SLAVE_ADDR)parameterBIT_CTRL = 1'b1 , //字地址位控制参数(16b/8b)parameterCLK_FREQ = 26'd50_000_000 , //i2c_dri模块的驱动时钟频率(CLK_FREQ)parameterI2C_FREQ = 18'd250_000, //I2C的SCL时钟频率parameterL_TIME = 17'd125_000, //led闪烁时间参数parameterMAX_BYTE = 16'd256 //读写测试的字节个数)(inputsys_clk,inputsys_rst_n,// i2c interface output i2c_scl,inouti2c_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 1250module 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;//---------------------------parameterr7 = 8'b1010_1111, w7 = 8'b1010_1110, //main7r6 = 8'b1010_1101, w6 = 8'b1010_1100, //main6r5 = 8'b1010_1011, w5 = 8'b1010_1010, //main5r4 = 8'b1010_1001, w4 = 8'b1010_1000, //main4r3 = 8'b1010_0111, w3 = 8'b1010_0110, //main3r2 = 8'b1010_0101, w2 = 8'b1010_0100, //main2r1 = 8'b1010_0011, w1 = 8'b1010_0010, //main1r0 = 8'b1010_0001, w0 = 8'b1010_0000; //main0assign sda = (out_flag == 1) ? sda_buf[7] : 1'bz;initialbeginaddr_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;endalways@(negedge sda)beginif(scl == 1)beginState = State + 1;if(State == 2'b11)disable write_to_eeprom;endendalways@(posedge sda)beginif(scl == 1) stop_W_R;elsebegincasex(State)2'b01:beginread_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)beginState = 2'b10;write_to_eeprom; endelseState = 2'b00;end2'b11:read_from_eeprom;default:State = 2'b00;endcaseendend task stop_W_R;beginState = 2'b00;addr_byte_h = 0;addr_byte_l = 0;ctrl_byte = 0;out_flag = 0;sda_buf = 0;endendtasktask read_in;beginshift_in(ctrl_byte);shift_in(addr_byte_h);shift_in(addr_byte_l);endendtasktask write_to_eeprom;beginshift_in(memory_buf);address = {addr_byte_h[4:0], addr_byte_l};memory[address] = memory_buf;State = 2'b00;endendtasktask read_from_eeprom;beginshift_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)beginaddress = {addr_byte_h[4:0], addr_byte_l};sda_buf = memory[address];shift_out;State = 2'b00;endendendtasktask 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;endendendtasktask shift_out;beginout_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;endendtaskendmodule
e2prom_tb.v
modulee2prom_tb;//parameterdefineparameterT = 20; //时钟周期为20nsparameterSLAVE_ADDR = 7'b1010000 ; //器件地址(SLAVE_ADDR)parameterBIT_CTRL = 1'b1 ; //字地址位控制参数(16b/8b)parameterCLK_FREQ = 26'd50_000_000 ; //i2c_dri模块的驱动时钟频率(CLK_FREQ)parameterI2C_FREQ = 18'd250_000; //I2C的SCL时钟频率parameterL_TIME = 17'd1; //led闪烁时间参数parameterMAX_BYTE = 16'd3; //读写测试的字节个数//reg defineregsys_clk; //时钟信号regsys_rst_n; //复位信号//wire definewire 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结构 进行状态机的 书写
建议 写成经典的三段状态机的形式
- 同步时序描述状态转移
- 组合逻辑判断状态转移条件
- 时序电路描述状态输出
前两部分是相对来说好处理的 后面第三部分的 时序逻辑电路描述状态有些复杂
代码第639行 和之前的存储地址一样又 不太一样
需要先 交还给 FPGA端控制权 再执行
为什么在之前的传递数据在cnt = 0 的时候 交还了控制权 直接赋值呢
原因是因为 它们并不是传递地址 只有首次传递地址的时候 需要保证在SCL在拉高的情况下 SDA先拉底
我们采用倒推法
cnt | 35 | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|---|
SCL | 1->0 | 0 | 0->1 | 1 | 1->0 | 0 |
实验效果 在 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```上面是别人的对的 我自己改成高电平触发 也是正确的 ```verilogcase(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```