赞
踩
下面是 design 设计
module IIC_CONTROL #( parameter SLAVE_ADDR = 7'b1010000 , // E2PROM 从机地址 parameter CLK_FREQ = 26'd50_000_000 , // 50MHz 的时钟频率 parameter I2C_FREQ = 18'd250_000 // SCL 的时钟频率 ) ( input clk , input rst_n , // ---------------------------------------------- // input [15 : 0] i2c_addr , // 地址 input [7 : 0] i2c_data_w , // 数据 input i2c_rh_wl , // 判断 是 read or write input bit_control , // 1是 16位 0 是 8位 input i2c_exec , // ------------------------------------------------ // output reg dri_clk , output reg [7 : 0] i2c_data_r , output reg i2c_ack , output reg i2c_done , // -------------------------------------------------- // output reg scl , inout sda ); // --------------------------------------------------------// // next is define // // --------------------------------------------------------// reg [9 : 0] clk_cnt ; wire [8 : 0] dri_cnt ; reg [2 : 0] state ; reg [2 : 0] next_state ; reg st_done ; // 在 状态机里面用来提示数据完成可以跳转 reg sda_dir ; // sda方向控制器 reg sda_out ; // 选择FPGA输入模式之后赋予sda线上 wire sda_in ; // sda输入信号 reg [6 : 0] cnt ; // 我们为了第三部分状态机而准备的 reg [15: 0] addr_save ; // 地址存储 reg [7 : 0] data_w_save ; // 数据写的暂存 reg wr_flag ; // 0 是 写 1 是 读 // 这三个是 暂存的方便调度的 reg [7 : 0] data_r_save ; // 读到的数据存储方便整合 // --------------------------------------------------------- // // parameter define // parameter st_idle = 3'b000 ; // 空闲状态 parameter st_sladdr = 3'b001 ; // 发送器件地址 parameter st_addr16 = 3'b010 ; // 发送高八位地址 parameter st_addr8 = 3'b011 ; // 发送低八位地址 parameter st_data_wr = 3'b100 ; // 写数据 parameter st_addr_rd = 3'b101 ; // 再次发送器件地址读 parameter st_data_rd = 3'b110 ; // 读数据 parameter st_stop = 3'b111 ; // 结束操作停止位 // ---------------------------------------------------- // // next is main code // // -------------------------------------------------------// assign dri_cnt = (CLK_FREQ/I2C_FREQ ) >> 2 ; always@(posedge clk or negedge rst_n ) begin if(rst_n == 0) begin dri_clk <= 0 ; clk_cnt <= 0 ; end else if( clk_cnt == dri_cnt[8:1] - 1) begin clk_cnt <= 0 ; dri_clk <= ~dri_clk ; end else begin dri_clk <= dri_clk ; clk_cnt <= clk_cnt + 1 ; end end // 下面开始状态机的叙述 // 同步时序描述状态转移 always@(posedge dri_clk or negedge rst_n) begin if(rst_n == 0) begin state <= st_idle ; end // 处于空闲状态 else begin state <= next_state ; end end // 组合逻辑判断状态转移条件 always@(*) begin next_state <= st_idle ; case(state) st_idle : begin if(i2c_exec == 1) begin next_state <= st_sladdr ; end else begin next_state <= st_idle ; end end // 当触发了i2c_exec 时候 可以由 空闲状态转移到 st_sladdr : begin if(st_done == 1) begin if(bit_control == 1) next_state <= st_addr16 ; else next_state <= st_addr8 ; end else begin next_state <= st_sladdr ; end end // 当 触发了 st_done 之后 通过 bit_control 选择是低八位 还是高八位的传输 st_addr16 : begin if(st_done == 1) begin next_state <= st_addr8 ; end else begin next_state <= st_addr16 ; end end // 高位 用完 轮到 低位的 传输 st_addr8 : begin if(st_done == 1) begin if(wr_flag == 0) next_state <= st_data_wr ; else next_state <= st_addr_rd ; end else begin next_state <= st_addr8 ; end end // 先来判断 写数据的 st_data_wr 数据代号是 4 st_data_wr : begin if(st_done == 1) begin next_state <= st_stop ; end else begin next_state <= st_data_wr ; end end // st_addr_rd : begin if(st_done == 1) begin next_state <= st_data_rd ; end else begin next_state <= st_addr_rd ; end end // st_data_rd : begin if(st_done == 1) begin next_state <= st_stop ; end else begin next_state <= st_data_rd ; end end // st_stop : begin if(st_done == 1) begin next_state <= st_idle ; end else begin next_state <= st_stop ; end end default: next_state <= st_idle ; endcase end / 下面来考虑另一个状态机的第三部分 --- 时序电路描述状态输出 // 设置一个变量 来控制 SDA的朝向 assign sda = sda_dir ? sda_out : 1'bz ; // sda_dir 为1 FPGA控制 assign sda_in = sda ; // 把sda当成了输出 always@(posedge dri_clk or negedge rst_n ) begin if( rst_n == 0) begin //首先根据输入输出 来判断 SCL 与 SDA 必须都为高 scl <= 1 ; sda_dir <= 1 ; sda_out <= 1 ; // 剩下的输出 i2c_data_r(输出) == data_r_save i2c_data_r <= 0 ; data_r_save <= 0 ; // 下面是端口的另外两个输出 i2c_ack 和 i2c_done i2c_ack <= 0 ; i2c_done <= 0 ; // 接下里是 内部信号的调节 这两个一个是内部后续的计数 还有一个本次case完成的结束信号 cnt <= 0 ; st_done <= 0 ; // 下面是三个暂存信号一个是 读写标志位 还有 传入的地址暂存 传入的数据暂存 wr_flag <= 0 ; addr_save <= 0 ; data_w_save <= 0 ; end else begin st_done <= 0 ; // 脉冲信号 cnt <= cnt + 1 ; //这里写在了 case之前就代表了 不用刻意在内部去调配 st_done 或是cnt case(state) st_idle : begin scl <= 1 ; sda_dir <= 1 ; sda_out <= 1 ; //这两个写不写不所谓 因为根本没用到 i2c_data_r <= 0 ; data_r_save <= 0 ; i2c_done <= 0 ; // cnt <= 0 ; st_done <= 0 ; // 开始 if( i2c_exec == 1) begin wr_flag <= i2c_rh_wl ; addr_save <= i2c_addr ; data_w_save <= i2c_data_w ; i2c_ack <= 0 ; end end // 这里先传递的是 st_sladdr : begin case(cnt) 7'd1 : sda_out <= 0 ; 7'd3 : scl <= 0 ; 7'd4 : sda_out <= SLAVE_ADDR[6] ; 7'd5 : scl <= 1'b1 ; 7'd7 : scl <= 1'b0 ; 7'd8 : sda_out <= SLAVE_ADDR[5] ; 7'd9 : scl <= 1'b1 ; 7'd11 : scl <= 1'b0 ; 7'd12 : sda_out <= SLAVE_ADDR[4] ; 7'd13 : scl <= 1'b1 ; 7'd15 : scl <= 1'b0 ; 7'd16 : sda_out <= SLAVE_ADDR[3] ; 7'd17 : scl <= 1'b1 ; 7'd19 : scl <= 1'b0 ; 7'd20 : sda_out <= SLAVE_ADDR[2] ; 7'd21 : scl <= 1'b1 ; 7'd23 : scl <= 1'b0 ; 7'd24 : sda_out <= SLAVE_ADDR[1] ; 7'd25 : scl <= 1'b1 ; 7'd27 : scl <= 1'b0 ; 7'd28 : sda_out <= SLAVE_ADDR[0] ; 7'd29 : scl <= 1'b1 ; 7'd31 : scl <= 1'b0 ; 7'd32 : sda_out <= 1'b0 ; // 此处完成了 数据的传递 接下来的任务是 反馈 7'd33 : scl <= 1'b1 ; 7'd35 : scl <= 1'b0 ; 7'd36 : sda_dir <= 1'b0 ; // 下放控制权给从机端口 7'd37 : scl <= 1'b1 ; // 下一时刻判断是否 有正确的反馈拉低 并确定 st_done = 1 7'd38 : begin st_done <= 1'b1 ; if( sda_in == 1) i2c_ack <= 1'b1 ; end 7'd39 : begin scl <= 1'b0 ; cnt <= 7'b0 ; end default : ; endcase end //发送高8位字节 st_addr16 : begin case(cnt) 7'd0 : begin // 39之后移动一格就是0 0 此处即可以开始 //把使能交还给FPGA端 sda_dir <= 1'b1 ; sda_out <= addr_save[15] ; end // 第一个转换有点时序差距 后面都是 每隔4 sda变化一次 7'd1 : scl <= 1'b1 ; 7'd3 : scl <= 1'b0 ; 7'd4 : sda_out <= addr_save[14] ; 7'd5 : scl <= 1'b1 ; 7'd7 : scl <= 1'b0 ; 7'd8 : sda_out <= addr_save[13] ; 7'd9 : scl <= 1'b1 ; 7'd11 : scl <= 1'b0 ; 7'd12 : sda_out <= addr_save[12] ; 7'd13 : scl <= 1'b1 ; 7'd15 : scl <= 1'b0 ; 7'd16 : sda_out <= addr_save[11] ; 7'd17 : scl <= 1'b1 ; 7'd19 : scl <= 1'b0 ; 7'd20 : sda_out <= addr_save[10] ; 7'd21 : scl <= 1'b1 ; 7'd23 : scl <= 1'b0 ; 7'd24 : sda_out <= addr_save[9] ; 7'd25 : scl <= 1'b1 ; 7'd27 : scl <= 1'b0 ; 7'd28 : sda_out <= addr_save[8] ; // 29 拉升 31下降 32放控制权 33拉升 34结束并作判断 35 拉低 cnt归零为下一状态准备 7'd29 : scl <= 1'b1 ; 7'd31 : scl <= 1'b0 ; 7'd32 : sda_dir <= 1'b0 ; 7'd33 : scl <= 1'b1 ; 7'd34 : begin st_done <= 1'b1 ; //完成 if(sda_in == 1) i2c_ack <= 1'b1 ; // scl拉高时 反馈 i2c_ack = 1 表示有错误 end 7'd35 : begin scl <= 1'b0 ; cnt <= 7'b0 ; end default : ; endcase end //发送低8位字节 st_addr8 : begin // 和上面这个写法是一样的 对于cnt = 0 sda_dir 交回FPGA控制权 并立刻赋值 case(cnt) 7'd0: begin sda_dir <= 1'b1 ; sda_out <= addr_save[7]; //字地址 end 7'd1 : scl <= 1'b1; 7'd3 : scl <= 1'b0; 7'd4 : sda_out <= addr_save[6]; 7'd5 : scl <= 1'b1; 7'd7 : scl <= 1'b0; 7'd8 : sda_out <= addr_save[5]; 7'd9 : scl <= 1'b1; 7'd11 : scl <= 1'b0; 7'd12 : sda_out <= addr_save[4]; 7'd13 : scl <= 1'b1; 7'd15 : scl <= 1'b0; 7'd16 : sda_out <= addr_save[3]; 7'd17 : scl <= 1'b1; 7'd19 : scl <= 1'b0; 7'd20 : sda_out <= addr_save[2]; 7'd21 : scl <= 1'b1; 7'd23 : scl <= 1'b0; 7'd24 : sda_out <= addr_save[1]; 7'd25 : scl <= 1'b1; 7'd27 : scl <= 1'b0; 7'd28 : sda_out <= addr_save[0]; 7'd29 : scl <= 1'b1 ; 7'd31 : scl <= 1'b0 ; 7'd32 : sda_dir <= 1'b0 ; 7'd33 : scl <= 1'b1 ; 7'd34 : begin st_done <= 1'b1 ; //完成 if(sda_in == 1) i2c_ack <= 1'b1 ; // scl拉高时 反馈 i2c_ack = 1 表示有错误 end 7'd35 : begin scl <= 1'b0 ; cnt <= 7'b0 ; end default : ; endcase end // st_data_wr : begin // 和上面这个写法是一样的 对于cnt = 0 sda_dir 交回FPGA控制权 并立刻赋值 case(cnt) 7'd0: begin sda_dir <= 1'b1 ; sda_out <= data_w_save[7]; //字地址 end 7'd1 : scl <= 1'b1; 7'd3 : scl <= 1'b0; 7'd4 : sda_out <= data_w_save[6]; 7'd5 : scl <= 1'b1; 7'd7 : scl <= 1'b0; 7'd8 : sda_out <= data_w_save[5]; 7'd9 : scl <= 1'b1; 7'd11 : scl <= 1'b0; 7'd12 : sda_out <= data_w_save[4]; 7'd13 : scl <= 1'b1; 7'd15 : scl <= 1'b0; 7'd16 : sda_out <= data_w_save[3]; 7'd17 : scl <= 1'b1; 7'd19 : scl <= 1'b0; 7'd20 : sda_out <= data_w_save[2]; 7'd21 : scl <= 1'b1; 7'd23 : scl <= 1'b0; 7'd24 : sda_out <= data_w_save[1]; 7'd25 : scl <= 1'b1; 7'd27 : scl <= 1'b0; 7'd28 : sda_out <= data_w_save[0]; // 29 拉升 31下降 32放控制权 33拉升 34结束并作判断 35 拉低 cnt归零为下一状态准备 7'd29 : scl <= 1'b1 ; 7'd31 : scl <= 1'b0 ; 7'd32 : sda_dir <= 1'b0 ; 7'd33 : scl <= 1'b1 ; 7'd34 : begin st_done <= 1'b1 ; //完成 if(sda_in == 1) i2c_ack <= 1'b1 ; // scl拉高时 反馈 i2c_ack = 1 表示有错误 end 7'd35 : begin scl <= 1'b0 ; cnt <= 7'b0 ; end default : ; endcase end // 读控制信号 可以开始读了 st_addr_rd : begin // 这里的过程应该和上面的那个 st_sladdr一样 先写地址 // 一样又不太一样 case(cnt) 7'd0 : begin sda_dir <= 1'b1; sda_out <= 1'b1; end 7'd1 : scl <= 1'b1; 7'd2 : sda_out <= 1'b0; //重新开始 7'd3 : scl <= 1'b0; 7'd4 : sda_out <= SLAVE_ADDR[6]; //传送器件地址 7'd5 : scl <= 1'b1; 7'd7 : scl <= 1'b0; 7'd8 : sda_out <= SLAVE_ADDR[5]; 7'd9 : scl <= 1'b1; 7'd11: scl <= 1'b0; 7'd12: sda_out <= SLAVE_ADDR[4]; 7'd13: scl <= 1'b1; 7'd15: scl <= 1'b0; 7'd16: sda_out <= SLAVE_ADDR[3]; 7'd17: scl <= 1'b1; 7'd19: scl <= 1'b0; 7'd20: sda_out <= SLAVE_ADDR[2]; 7'd21: scl <= 1'b1; 7'd23: scl <= 1'b0; 7'd24: sda_out <= SLAVE_ADDR[1]; 7'd25: scl <= 1'b1; 7'd27: scl <= 1'b0; 7'd28: sda_out <= SLAVE_ADDR[0]; 7'd29: scl <= 1'b1; 7'd31: scl <= 1'b0; 7'd32: sda_out <= 1'b1; //1:读 7'd33: scl <= 1'b1; 7'd35: scl <= 1'b0; 7'd36: begin sda_dir <= 1'b0; sda_out <= 1'b1; end 7'd37: scl <= 1'b1; 7'd38: begin //从机应答 st_done <= 1'b1; if(sda_in == 1'b1) //高电平表示未应答 i2c_ack <= 1'b1; //拉高应答标志位 end 7'd39: begin scl <= 1'b0; cnt <= 7'b0; end default : ; endcase end st_data_rd : begin //读取数据(8 bit) case(cnt) 7'd0: sda_dir <= 1'b0; 7'd1: scl <= 1'b1; 7'd2 : data_r_save[7] <= sda_in; 7'd3: scl <= 1'b0; 7'd5: scl <= 1'b1 ; 7'd6: data_r_save[6] <= sda_in ; 7'd7: scl <= 1'b0; 7'd9: scl <= 1'b1 ; 7'd10 : data_r_save[5] <= sda_in; 7'd11: scl <= 1'b0; 7'd13: scl <= 1'b1 ; 7'd14: data_r_save[4] <= sda_in; 7'd15: scl <= 1'b0; 7'd17: scl <= 1'b1 ; 7'd18: data_r_save[3] <= sda_in; 7'd19: scl <= 1'b0; 7'd21: scl <= 1'b1 ; 7'd22: data_r_save[2] <= sda_in; 7'd23: scl <= 1'b0; 7'd25: scl <= 1'b1 ; 7'd26: data_r_save[1] <= sda_in; 7'd27: scl <= 1'b0; 7'd29: scl <= 1'b1 ; 7'd30: data_r_save[0] <= sda_in; 7'd31: scl <= 1'b0; 7'd32: begin sda_dir <= 1'b1; sda_out <= 1'b1; end 7'd33: scl <= 1'b1; 7'd34: st_done <= 1'b1; //非应答 7'd35: begin scl <= 1'b0; cnt <= 7'b0; i2c_data_r <= data_r_save; end default : ; endcase end st_stop: begin //结束I2C操作 case(cnt) 7'd0: begin sda_dir <= 1'b1; //结束I2C sda_out <= 1'b0; end 7'd1 : scl <= 1'b1; 7'd3 : sda_out <= 1'b1; 7'd15: st_done <= 1'b1; 7'd16: begin cnt <= 7'b0; i2c_done <= 1'b1; //向上层模块传递I2C结束信号 end default : ; endcase end endcase end end endmodule
module E2PROM #( // //EEPROM写数据需要添加间隔时间,读数据则不需要 parameter WR_WAIT_TIME = 14'd5000, //写入间隔时间 parameter MAX_BYTE = 16'd256 //读写测试的字节个数 ) ( input clk , input rst_n , // from I2C-control input [7 : 0] i2c_data_r , // 读出来的数据 input i2c_ack , // 应答 input i2c_done , // i2c完成信号 // give to i2c output reg i2c_exec , output reg i2c_rh_wl , output reg [15 : 0] i2c_addr , output reg [7 : 0] i2c_data_w , // gvie it to led output reg rw_done , // e2prom 读写测试完成 output reg rw_result // e2prom 的结果 0 : 失败 1 :成功 ); // reg define reg [1:0] flow_cnt ; //状态流控制 reg [13:0] wait_cnt ; //延时计数器 always@(posedge clk or negedge rst_n) begin if(rst_n == 0) begin i2c_exec <= 0 ; i2c_rh_wl <= 0 ; i2c_addr <= 0 ; i2c_data_w <= 0 ; rw_done <= 0 ; rw_result <= 0 ; flow_cnt <= 0 ; wait_cnt <= 0 ; end else begin i2c_exec <= 0 ; // 把 i2c_ecec 看成是一个脉冲信号 case(flow_cnt) 2'd0 : begin if(wait_cnt == (WR_WAIT_TIME - 1) ) begin wait_cnt <= 0 ; if(i2c_addr == MAX_BYTE) begin // 表示256个数据写入其中 i2c_addr <= 16'b0; i2c_rh_wl <= 1'b1; flow_cnt <= 2'd2; end else begin flow_cnt <= flow_cnt + 2'b1; i2c_exec <= 1'b1; end end else begin wait_cnt <= wait_cnt + 1 ; end end 2'd1 : begin if(i2c_done == 1'b1) begin //EEPROM单次写入完成 flow_cnt <= 2'd0; i2c_addr <= i2c_addr + 16'b1; //地址0~255分别写入 i2c_data_w <= i2c_data_w + 8'b1; //数据0~255 end else begin flow_cnt <= flow_cnt ; i2c_addr <= i2c_addr ; i2c_data_w <= i2c_data_w ; end end 2'd2 : begin flow_cnt <= flow_cnt + 2'b1; i2c_exec <= 1'b1; end 2'd3 : begin if(i2c_done == 1'b1) begin //EEPROM单次读出完成 //读出的值错误或者I2C未应答,读写测试失败 if((i2c_addr[7:0] != i2c_data_r) || (i2c_ack == 1'b1)) begin rw_done <= 1'b1; rw_result <= 1'b0; end else if(i2c_addr == (MAX_BYTE - 16'b1))begin //读写测试成功 rw_done <= 1'b1; rw_result <= 1'b1; end else begin flow_cnt <= 2'd2; i2c_addr <= i2c_addr + 16'b1; end end end default : ; endcase end end endmodule
module LED #(parameter L_TIME = 17'd125_000 ) ( input clk , //时钟信号 input rst_n , //复位信号 input rw_done , //错误标志 input rw_result , //E2PROM读写测试完成 output reg led //E2PROM读写测试结果 0:失败 1:成功 ); //reg define reg rw_done_flag; //读写测试完成标志 reg [16:0] led_cnt ; //led计数 //***************************************************** //** main code //***************************************************** //读写测试完成标志 always @(posedge clk or negedge rst_n) begin if(!rst_n) rw_done_flag <= 1'b0; else if(rw_done) rw_done_flag <= 1'b1; end //错误标志为1时PL_LED0闪烁,否则PL_LED0常亮 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin led_cnt <= 17'd0; led <= 1'b0; end else begin if(rw_done_flag) begin if(rw_result) //读写测试正确 led <= 1'b1; //led灯常亮 else begin //读写测试错误 led_cnt <= led_cnt + 17'd1; if(led_cnt == (L_TIME - 17'b1)) begin led_cnt <= 17'd0; led <= ~led; //led灯闪烁 end else led <= led; end end else led <= 1'b0; //读写测试完成之前,led灯熄灭 end end endmodule
module IIC_top #( parameter SLAVE_ADDR = 7'b1010000 , //器件地址(SLAVE_ADDR) parameter BIT_CTRL = 1'b1 , //字地址位控制参数(16b/8b) parameter CLK_FREQ = 26'd50_000_000 , //i2c_dri模块的驱动时钟频率(CLK_FREQ) parameter I2C_FREQ = 18'd250_000 , //I2C的SCL时钟频率 parameter L_TIME = 17'd125_000 , //led闪烁时间参数 parameter MAX_BYTE = 16'd256 //读写测试的字节个数 )( input sys_clk , input sys_rst_n , // i2c interface output i2c_scl , inout i2c_sda , // led output led ); wire dri_clk ; //I2C操作时钟 wire i2c_exec ; //I2C触发控制 wire [15:0] i2c_addr ; //I2C操作地址 wire [ 7:0] i2c_data_w; //I2C写入的数据 wire i2c_done ; //I2C操作结束标志 wire i2c_ack ; //I2C应答标志 0:应答 1:未应答 wire i2c_rh_wl ; //I2C读写控制 wire [ 7:0] i2c_data_r; //I2C读出的数据 wire rw_done ; //E2PROM读写测试完成 wire rw_result ; //E2PROM读写测试结果 0:失败 1:成功 E2PROM#( .WR_WAIT_TIME ( 14'd5000 ), .MAX_BYTE ( MAX_BYTE ) )u_E2PROM( .clk ( dri_clk ), .rst_n ( sys_rst_n ), .i2c_data_r ( i2c_data_r ), .i2c_ack ( i2c_ack ), .i2c_done ( i2c_done ), .i2c_exec ( i2c_exec ), .i2c_rh_wl ( i2c_rh_wl ), .i2c_addr ( i2c_addr ), .i2c_data_w ( i2c_data_w ), .rw_done ( rw_done ), .rw_result ( rw_result ) ); IIC_CONTROL#( .SLAVE_ADDR ( SLAVE_ADDR ), .CLK_FREQ ( CLK_FREQ ), .I2C_FREQ ( I2C_FREQ ) )u_IIC_CONTROL( .clk ( sys_clk ), .rst_n ( sys_rst_n ), .i2c_addr ( i2c_addr ), .i2c_data_w ( i2c_data_w ), .i2c_rh_wl ( i2c_rh_wl ), .bit_control ( BIT_CTRL ), .i2c_exec ( i2c_exec ), .dri_clk ( dri_clk ), .i2c_data_r ( i2c_data_r ), .i2c_ack ( i2c_ack ), .i2c_done ( i2c_done ), .scl ( i2c_scl ), .sda ( i2c_sda ) ); LED#( .L_TIME ( L_TIME ) )u_LED( .clk ( dri_clk ), .rst_n ( sys_rst_n ), .rw_done ( rw_done ), .rw_result ( rw_result ), .led ( led ) ); endmodule
下面是testbench
`timescale 1ns/1ns `define timeslice 1250 module EEPROM_AT24C64( scl, sda ); input scl; inout sda; reg out_flag; reg[7:0] memory[8191:0]; reg[12:0]address; reg[7:0]memory_buf; reg[7:0]sda_buf; reg[7:0]shift; reg[7:0]addr_byte_h; reg[7:0]addr_byte_l; reg[7:0]ctrl_byte; reg[1:0]State; integer i; //--------------------------- parameter r7 = 8'b1010_1111, w7 = 8'b1010_1110, //main7 r6 = 8'b1010_1101, w6 = 8'b1010_1100, //main6 r5 = 8'b1010_1011, w5 = 8'b1010_1010, //main5 r4 = 8'b1010_1001, w4 = 8'b1010_1000, //main4 r3 = 8'b1010_0111, w3 = 8'b1010_0110, //main3 r2 = 8'b1010_0101, w2 = 8'b1010_0100, //main2 r1 = 8'b1010_0011, w1 = 8'b1010_0010, //main1 r0 = 8'b1010_0001, w0 = 8'b1010_0000; //main0 assign sda = (out_flag == 1) ? sda_buf[7] : 1'bz; initial begin addr_byte_h = 0; addr_byte_l = 0; ctrl_byte = 0; out_flag = 0; sda_buf = 0; State = 2'b00; memory_buf = 0; address = 0; shift = 0; for(i=0;i<=8191;i=i+1) memory[i] = 0; end always@(negedge sda) begin if(scl == 1) begin State = State + 1; if(State == 2'b11) disable write_to_eeprom; end end always@(posedge sda) begin if(scl == 1) stop_W_R; else begin casex(State) 2'b01:begin read_in; if(ctrl_byte == w7 || ctrl_byte == w6 || ctrl_byte == w5 || ctrl_byte == w4 || ctrl_byte == w3 || ctrl_byte == w2 || ctrl_byte == w1 || ctrl_byte == w0) begin State = 2'b10; write_to_eeprom; end else State = 2'b00; end 2'b11: read_from_eeprom; default: State = 2'b00; endcase end end task stop_W_R; begin State = 2'b00; addr_byte_h = 0; addr_byte_l = 0; ctrl_byte = 0; out_flag = 0; sda_buf = 0; end endtask task read_in; begin shift_in(ctrl_byte); shift_in(addr_byte_h); shift_in(addr_byte_l); end endtask task write_to_eeprom; begin shift_in(memory_buf); address = {addr_byte_h[4:0], addr_byte_l}; memory[address] = memory_buf; State = 2'b00; end endtask task read_from_eeprom; begin shift_in(ctrl_byte); if(ctrl_byte == r7 || ctrl_byte == w6 || ctrl_byte == r5 || ctrl_byte == r4 || ctrl_byte == r3 || ctrl_byte == r2 || ctrl_byte == r1 || ctrl_byte == r0) begin address = {addr_byte_h[4:0], addr_byte_l}; sda_buf = memory[address]; shift_out; State = 2'b00; end end endtask task shift_in; output[7:0]shift; begin @(posedge scl) shift[7] = sda; @(posedge scl) shift[6] = sda; @(posedge scl) shift[5] = sda; @(posedge scl) shift[4] = sda; @(posedge scl) shift[3] = sda; @(posedge scl) shift[2] = sda; @(posedge scl) shift[1] = sda; @(posedge scl) shift[0] = sda; @(negedge scl) begin #(`timeslice); out_flag = 1; sda_buf = 0; end @(negedge scl) begin #(`timeslice-250); out_flag = 0; end end endtask task shift_out; begin out_flag = 1; for(i=6; i>=0; i=i-1) begin @(negedge scl); #`timeslice; sda_buf = sda_buf << 1; end @(negedge scl) #`timeslice sda_buf[7] = 1; @(negedge scl) #`timeslice out_flag = 0; end endtask endmodule
module e2prom_tb; //parameter define parameter T = 20 ; //时钟周期为20ns parameter SLAVE_ADDR = 7'b1010000 ; //器件地址(SLAVE_ADDR) parameter BIT_CTRL = 1'b1 ; //字地址位控制参数(16b/8b) parameter CLK_FREQ = 26'd50_000_000 ; //i2c_dri模块的驱动时钟频率(CLK_FREQ) parameter I2C_FREQ = 18'd250_000 ; //I2C的SCL时钟频率 parameter L_TIME = 17'd1 ; //led闪烁时间参数 parameter MAX_BYTE = 16'd3 ; //读写测试的字节个数 //reg define reg sys_clk ; //时钟信号 reg sys_rst_n; //复位信号 //wire define wire iic_scl; wire iic_sda; wire led ; //***************************************************** //** main code //***************************************************** //给输入信号初始值 initial begin sys_clk = 1'b0; sys_rst_n = 1'b0; //复位 #(T+1) sys_rst_n = 1'b1; //在第21ns的时候复位信号信号拉高 end //50Mhz的时钟,周期则为1/50Mhz=20ns,所以每10ns,电平取反一次 always #(T/2) sys_clk = ~sys_clk; //将SDA数据线上拉 pullup(iic_sda); IIC_top#( .SLAVE_ADDR ( 7'b1010000 ), .BIT_CTRL ( 1'b1 ), .CLK_FREQ ( 26'd50_000_000 ), .I2C_FREQ ( 18'd250_000 ), .L_TIME ( 17'd125_000 ), .MAX_BYTE ( 16'd256 ) )u_IIC_top( .sys_clk ( sys_clk ), .sys_rst_n ( sys_rst_n ), .i2c_scl ( iic_scl ), .i2c_sda ( iic_sda ), .led ( led ) ); //例化e2prom仿真模型 EEPROM_AT24C64 u_EEPROM_AT24C64( .scl (iic_scl), .sda (iic_sda) ); endmodule
下面是注意事项
因为时钟的不同 我们先设计出本次时钟所需要的dri_clk
在配置完dri_clk 之后 我们需要做的是对整个I2C结构 进行状态机的 书写
建议 写成经典的三段状态机的形式
前两部分是相对来说好处理的 后面第三部分的 时序逻辑电路描述状态有些复杂
需要先 交还给 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 因为时钟总是在一半的时候进行翻转
完整的画一下 单次读 与 单次写的 时序流程图
下面是单次写的时序图
最后上板验证也未出现问题
// if( i2c_exec == 1) begin wr_flag <= i2c_rh_wl ; addr_save <= i2c_addr ; data_w_save <= i2c_data_w ; i2c_ack <= 0 ; // end ``` # 第734行 它的做法总是在上升沿触发 我吐槽过了 我觉得电平触发更加稳定 它的做法 ```verilog case(cnt) 7'd0: sda_dir <= 1'b0; 7'd1: begin data_r_save[7] <= sda_in; scl <= 1'b1; end 7'd3: scl <= 1'b0; 7'd5: begin data_r_save[6] <= sda_in ; scl <= 1'b1 ; end 7'd7: scl <= 1'b0; 7'd9: begin data_r_save[5] <= sda_in; scl <= 1'b1 ; end 7'd11: scl <= 1'b0; 7'd13: begin data_r_save[4] <= sda_in; scl <= 1'b1 ; end 7'd15: scl <= 1'b0; 7'd17: begin data_r_save[3] <= sda_in; scl <= 1'b1 ; end 7'd19: scl <= 1'b0; 7'd21: begin data_r_save[2] <= sda_in; scl <= 1'b1 ; end 7'd23: scl <= 1'b0; 7'd25: begin data_r_save[1] <= sda_in; scl <= 1'b1 ; end 7'd27: scl <= 1'b0; 7'd29: begin data_r_save[0] <= sda_in; scl <= 1'b1 ; end ``` 上面是别人的对的 我自己改成高电平触发 也是正确的 ```verilog case(cnt) 7'd0: sda_dir <= 1'b0; 7'd1: scl <= 1'b1; 7'd2 : data_r_save[7] <= sda_in; 7'd3: scl <= 1'b0; 7'd5: scl <= 1'b1 ; 7'd6: data_r_save[6] <= sda_in ; 7'd7: scl <= 1'b0; 7'd9: scl <= 1'b1 ; 7'd10 : data_r_save[5] <= sda_in; 7'd11: scl <= 1'b0; 7'd13: scl <= 1'b1 ; 7'd14: data_r_save[4] <= sda_in; 7'd15: scl <= 1'b0; 7'd17: scl <= 1'b1 ; 7'd18: data_r_save[3] <= sda_in; 7'd19: scl <= 1'b0; 7'd21: scl <= 1'b1 ; 7'd22: data_r_save[2] <= sda_in; 7'd23: scl <= 1'b0; 7'd25: scl <= 1'b1 ; 7'd26: data_r_save[1] <= sda_in; 7'd27: scl <= 1'b0; 7'd29: scl <= 1'b1 ; 7'd30: data_r_save[0] <= sda_in; 7'd31: scl <= 1'b0; 7'd32: begin sda_dir <= 1'b1; sda_out <= 1'b1; end 7'd33: scl <= 1'b1; 7'd34: st_done <= 1'b1; //非应答 7'd35: begin scl <= 1'b0; cnt <= 7'b0; i2c_data_r <= data_r_save; end default : ; endcase end ```
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。