赞
踩
IIC接口协议是一种比较简单、常用的一种接口协议,使用它的场景很广泛(最常见的如EEPROM读写、一些寄存器的配置等),是我们学习FPGA过程中常用的接口协议,本篇使用EEPROM(AT24C512B)器件作为目标器件进行IIC接口实现,其他器件读写方式略有差别,IIC协议不变。
IIC (Inter-Integrated Circuit(集成电路总线)),百度百科解释如下:IIC-百度百科。
这里简单概括一下协议内容:IIC(IIC,inter-Integrated circuit),两线式串行总线,用于MCU和外设间的通信;IIC只需两根线进行数据传输(数据线SDA和时钟线SCL),以半双工方式实现MCU和外设之间数据传输,常用的传输速率为100kbps、400kbps。
注意:SDA和SCL两根总线需要上拉,使总线处于空闲状态。
IIC总线系统结构如下图:
IIC协议如下:
1.空闲状态
协议规定,SDA和SCL同时为高电平时,总线处于空闲状态。上拉电阻保证电平处于高电平。
2.起始信号和结束信号
起始信号:SCL为高电平时,SDA电平发生高到低的跳变。
停止信号:SCL为高电平时,SDA电平发生低到高的跳变。
3.应答信号
发送器每发送完一个字节(8个脉冲),在第9个脉冲间释放总线,接收器返回一个ACK信号,协议规定,低电平为有效应答,高电平为无效应答。
4.数据有效性
协议对有效数据进行了规定:即时钟信号为高电平期间,数据必须保持稳定,时钟信号低电平期间,数据线上的电平才允许变化。也就是说,数据在时钟信号到来前必须准备好,并保持到时钟信号的下降沿之后。
5.数据传输
I2C为同步传输,时钟控制数据位的传输,边沿触发。
这里只实现“字节写”和“随机读”读写方式,其他读写方式根据实际情况略作更改即可
1.字节写
在起始信号后第一个字节写入7bit设备地址和1bit读写信号,等待从机回复一个ACK信号,第二字节写入高字节寄存器地址,等待从机回复一个ACK信号,第三字节写入低字节寄存器地址(若地址只有八位,写入一个寄存器地址即可),等待从机回复一个ACK信号,第四个字节写入要写入的数据,等待从机回复一个ACK信号,最后是一个停止信号。
2.随机读
在起始信号后第一个字节写入7bit设备地址和1bit读写信号,等待从机回复一个ACK信号,第二字节写入高字节寄存器地址,等待从机回复一个ACK信号,第三字节写入低字节寄存器地址(若地址只有八位,写入一个寄存器地址即可),等待从机回复一个ACK信号(前面的3个字节是一个虚写操作);这里之后需要增加一个起始信号,第四个字节写入7bit设备地址和1bit读信号,等待从机回复一个ACK信号,第五个字节从机回复当前地址的数据,之后从机再回复一个NOACK信号,最后是一个停止信号。
1.宏定义文件
宏定义文件定义了3个宏,“SIM”用于仿真和综合的切换,综合时将“SIM”注释即可;“SIM_MODEL”用于添加仿真模型,默认即可;“FIRST_SIM”用于第一次仿真和非第一次仿真的切换,如果不是第一次仿真注释即可。
`define SIM
`define SIM_MODEL
// `define FIRST_SIM
2.IIC主机实现
IIC主机用于读写主控,这里使用了两个parameter,“IIC_RATE ”用于切换IIC速率(50K、100K、400K),“REGADDR_WIDTH ”用于切换读写地址位宽(8bit、16bit)
`timescale 1ns/1ns //------------------------------------------------------------------------------------------ `include "macro_define.v" `define SCL_POS (cnt_delay==scl_pos_cnt) //cnt=0:scl posedge `define SCL_HIG (cnt_delay==scl_hig_cnt) //cnt=1:scl high level :data stable `define SCL_NEG (cnt_delay==scl_neg_cnt) //cnt=2:scl negedge `define SCL_LOW (cnt_delay==scl_low_cnt) //cnt=3:scl low level :data change module iic_master #( parameter IIC_RATE = "100k", parameter REGADDR_WIDTH = 16 ) ( input wire clk , //50M input wire rst_n , // output wire scl , //IIC clock inout wire sda , //IIC data input wire iic_start , //IIC start input wire rd_or_wr , //1:read 0:write input wire[6:0] iic_device_id , //IIC device address input wire[REGADDR_WIDTH-1:0] iic_reg_addr , //register address input wire[7:0] iic_wr_data , //write register data output reg iic_rd_data_en , //IIC done flag output reg [7:0] iic_rd_data , //read register data output reg iic_done , //IIC done flag output reg iic_busy=0 //IIC busy flag ); //-------------------reg/wire--------------------------// reg iic_start_t1 ; reg iic_start_rise ; reg[3:0] cnt ; reg[15:0] cnt_delay ; wire scl_valid ; reg [15:0] delay_counter ; wire[15:0] scl_pos_cnt ; wire[15:0] scl_hig_cnt ; wire[15:0] scl_neg_cnt ; wire[15:0] scl_low_cnt ; wire[8*4-1:0] iic_rate ; reg scl_temp ; reg[7:0] db_r ; // reg[3:0] cstate ; // reg sda_r ; //sda output reg reg sda_link ; //0:output 1:input reg[3:0] num ; // reg ack_reg=0 ; //----------------------------------------------------// assign iic_rate = IIC_RATE ; assign scl_valid = iic_busy ; always @ (*) begin if(iic_rate=="50k") delay_counter <= 2000; else if(iic_rate=="100k") delay_counter <= 1000; else if(iic_rate=="400k") delay_counter <= 500; else delay_counter <= 250; end assign scl_pos_cnt = (delay_counter>>2)-1; assign scl_hig_cnt = (delay_counter>>1)-1; assign scl_neg_cnt = (delay_counter>>1)+(delay_counter>>2)-1; assign scl_low_cnt = delay_counter-1; //iic_start_rise always @ (posedge clk or negedge rst_n) if( !rst_n )begin iic_start_t1 <= 1'b0; iic_start_rise <= 1'b0; end else begin iic_start_t1 <= iic_start; iic_start_rise <= iic_start & ~iic_start_t1; end //cnt_delay always @ (posedge clk or negedge rst_n) if( !rst_n ) cnt_delay <= 10'd0; else if( scl_valid && cnt_delay == delay_counter-1 ) cnt_delay <= 10'd0; else if(scl_valid==1) cnt_delay <= cnt_delay+1'b1; else cnt_delay <= 10'd0; always @ (posedge clk or negedge rst_n) begin if( !rst_n ) cnt <= 3'd0; else begin case ( cnt_delay ) scl_neg_cnt: cnt <= 3'd0; scl_low_cnt: cnt <= 3'd1; scl_pos_cnt: cnt <= 3'd2; scl_hig_cnt: cnt <= 3'd3; default: ; endcase end end //产生iic所需要的时钟 always @ (posedge clk or negedge rst_n) if(!rst_n) scl_temp <= 1'b1; else if(cnt_delay==scl_pos_cnt) scl_temp <= 1'b1; //scl rise edge else if(cnt_delay==scl_neg_cnt) scl_temp <= 1'b0; //scl falling edge else scl_temp <= scl_temp; assign scl = (scl_valid==1)?scl_temp:1; reg iic_done_t; // always @ (posedge clk or negedge rst_n) if(!rst_n)begin iic_done_t <= 1'b0; end else begin iic_done_t <= iic_done; end always @ (posedge clk or negedge rst_n) if(!rst_n) iic_rd_data_en<= 1'b0; else if(rd_or_wr) iic_rd_data_en <= iic_done & ~iic_done_t; else iic_rd_data_en<= 1'b0; //---------------------------------------------// localparam IDLE = 4'd0 , START1 = 4'd1 , ADD1 = 4'd2 , ACK1 = 4'd3 , ADD2 = 4'd4 , ACK2 = 4'd5 , ADD2_1 = 4'd6 , ACK2_1 = 4'd7 , START2 = 4'd8 , ADD3 = 4'd9 , ACK3 = 4'd10 , DATA = 4'd11 , ACK4 = 4'd12 , STOP1 = 4'd13 , STOP2 = 4'd14 ; always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin cstate <= IDLE; sda_r <= 1'b1; sda_link <= 1'b1; num <= 4'd0; db_r <= 8'd0; iic_rd_data <= 8'b0000_0000; iic_done <= 1'b0; end else case (cstate) IDLE: begin sda_link <= 1'b1; //0:input;1:output sda_r <= 1'b1; iic_done <= 1'b0; if(iic_start_rise) begin //iic start `ifdef SIM db_r <= {iic_device_id,rd_or_wr}; //write device address rd_or_wr=1:read;rd_or_wr=0:write; // $display("simulation"); `else db_r <= {iic_device_id,1'b0}; //write device address rd_or_wr=1:read;rd_or_wr=0:write; // $display("synth"); `endif cstate <= START1; iic_busy <= 1; end else cstate <= IDLE; end START1: begin //iic start singal if(`SCL_HIG) begin //scl = 1; sda_link <= 1'b1; //0:input;1:output sda_r <= 1'b0; //iic start singal cstate <= ADD1; num <= 4'd0; //num clear end else cstate <= START1; end ADD1: begin //iic receive device id if(`SCL_LOW) begin if(num == 4'd8) begin num <= 4'd0; //num clear sda_r <= 1'b1; sda_link <= 1'b0; //0:input;1:output wait ack cstate <= ACK1; end else begin cstate <= ADD1; num <= num+1'b1; case (num) 4'd0: sda_r <= db_r[7]; 4'd1: sda_r <= db_r[6]; 4'd2: sda_r <= db_r[5]; 4'd3: sda_r <= db_r[4]; 4'd4: sda_r <= db_r[3]; 4'd5: sda_r <= db_r[2]; 4'd6: sda_r <= db_r[1]; 4'd7: sda_r <= db_r[0]; //read or warte default: ; endcase end end else cstate <= ADD1; end ACK1: begin //iic after transmit device id,receive slave ack singal if(`SCL_HIG) ack_reg <= sda; else if(`SCL_NEG) begin sda_r <= 1'b0; sda_link <= 1'b1; //0:input;1:output wait ack if(ack_reg==0 && REGADDR_WIDTH == 16)begin cstate <= ADD2; // if register address width is 16bit,go to status ADD2 db_r <= iic_reg_addr[15:8]; // Transmit High 8-bit Address first end else if(ack_reg==0 && REGADDR_WIDTH == 8)begin cstate <= ADD2_1; // if register address width is 8bit,go to status ADD2_1 db_r <= iic_reg_addr[7:0]; // Transmit 8-bit Address end else if(ack_reg!=0) cstate <= IDLE; end else cstate <= ACK1; //wait slave respond end ADD2: begin // Transmit High 8-bit Address first if(`SCL_LOW) begin if(num==4'd8) begin num <= 4'd0; //num计数清零 sda_r <= 1'b1; sda_link <= 1'b0; //sda置为高阻态(input) cstate <= ACK2; end else begin sda_link <= 1'b1; //sda作为output num <= num+1'b1; case (num) 4'd0: sda_r <= db_r[7]; 4'd1: sda_r <= db_r[6]; 4'd2: sda_r <= db_r[5]; 4'd3: sda_r <= db_r[4]; 4'd4: sda_r <= db_r[3]; 4'd5: sda_r <= db_r[2]; 4'd6: sda_r <= db_r[1]; 4'd7: sda_r <= db_r[0]; default: ; endcase cstate <= ADD2; end end else cstate <= ADD2; end ACK2: begin //iic transmit high 8-bit address finish,receive slave ack singal if(`SCL_HIG) ack_reg <= sda; else if(`SCL_NEG)begin sda_r <= 1'b0; sda_link <= 1'b1; //0:input;1:output wait ack if(ack_reg==0)begin cstate <= ADD2_1; //从机响应信号 db_r <= iic_reg_addr[7:0]; // 1地址 end else cstate <= IDLE; end else cstate <= ACK2; //等待从机响应 end ADD2_1: begin // if register address width is 16bit Transmit Low 8-bit Address if(`SCL_LOW) begin // if register address width is 8bit Transmit register Address if(num==4'd8) begin num <= 4'd0; //num计数清零 sda_r <= 1'b1; sda_link <= 1'b0; //sda置为高阻态(input) cstate <= ACK2_1; end else begin sda_link <= 1'b1; //sda作为output num <= num+1'b1; case (num) 4'd0: sda_r <= db_r[7]; 4'd1: sda_r <= db_r[6]; 4'd2: sda_r <= db_r[5]; 4'd3: sda_r <= db_r[4]; 4'd4: sda_r <= db_r[3]; 4'd5: sda_r <= db_r[2]; 4'd6: sda_r <= db_r[1]; 4'd7: sda_r <= db_r[0]; default: ; endcase cstate <= ADD2_1; end end else cstate <= ADD2_1; end ACK2_1: begin //iic transmit register address finish,receive slave ack singal if(`SCL_HIG) ack_reg <= sda; else if(`SCL_NEG)begin sda_r <= 1'b0; sda_link <= 1'b1; //0:input;1:output wait ack if(ack_reg==0)begin if(rd_or_wr) begin cstate <= START2; //读操作 db_r <= {iic_device_id,1'b1}; //送器件地址(读操作),特定地址读需要执行该步骤以下操作 end else begin cstate <= DATA; //写操作 db_r <= iic_wr_data; //写入的数据 end end else cstate <= IDLE; end else cstate <= ACK2_1; //等待从机响应 end START2: begin //读操作起始位 if(`SCL_LOW) begin sda_link <= 1'b1; //sda作为output sda_r <= 1'b1; //拉高数据线sda cstate <= START2; end else if(`SCL_HIG) begin //scl为高电平中间 sda_r <= 1'b0; //拉低数据线sda,产生起始位信号 cstate <= ADD3; end else cstate <= START2; end ADD3: begin //送读操作地址 if(`SCL_LOW) begin if(num==4'd8) begin num <= 4'd0; //num计数清零 sda_r <= 1'b1; sda_link <= 1'b0; //sda置为高阻态(input) cstate <= ACK3; end else begin num <= num+1'b1; case (num) 4'd0: sda_r <= db_r[7]; 4'd1: sda_r <= db_r[6]; 4'd2: sda_r <= db_r[5]; 4'd3: sda_r <= db_r[4]; 4'd4: sda_r <= db_r[3]; 4'd5: sda_r <= db_r[2]; 4'd6: sda_r <= db_r[1]; 4'd7: sda_r <= db_r[0]; default: ; endcase cstate <= ADD3; end end else cstate <= ADD3; end ACK3: begin if(`SCL_HIG) ack_reg <= sda; else if(`SCL_NEG)begin sda_r <= 1'b0; sda_link <= 1'b1; //0:input;1:output wait ack if(ack_reg==0)begin cstate <= DATA; //从机响应信号 sda_link <= 1'b0; end else cstate <= IDLE; end else cstate <= ACK3; //等待从机响应 end DATA: begin if(rd_or_wr) begin //读操作 if(num<=4'd7) begin cstate <= DATA; if(`SCL_HIG) begin num <= num+1'b1; case (num) 4'd0: iic_rd_data[7] <= sda; 4'd1: iic_rd_data[6] <= sda; 4'd2: iic_rd_data[5] <= sda; 4'd3: iic_rd_data[4] <= sda; 4'd4: iic_rd_data[3] <= sda; 4'd5: iic_rd_data[2] <= sda; 4'd6: iic_rd_data[1] <= sda; 4'd7: iic_rd_data[0] <= sda; default: ; endcase end end else if((`SCL_LOW) && (num==4'd8)) begin num <= 4'd0; //num计数清零 cstate <= ACK4; end else cstate <= DATA; end else begin //write data sda_link <= 1'b1; if(num<=4'd7) begin cstate <= DATA; if(`SCL_LOW) begin sda_link <= 1'b1; //数据线sda作为output num <= num+1'b1; case (num) 4'd0: sda_r <= db_r[7]; 4'd1: sda_r <= db_r[6]; 4'd2: sda_r <= db_r[5]; 4'd3: sda_r <= db_r[4]; 4'd4: sda_r <= db_r[3]; 4'd5: sda_r <= db_r[2]; 4'd6: sda_r <= db_r[1]; 4'd7: sda_r <= db_r[0]; default: ; endcase end end else if((`SCL_LOW) && (num==4'd8)) begin num <= 4'd0; sda_r <= 1'b1; sda_link <= 1'b0; //sda置为高阻态 cstate <= ACK4; end else cstate <= DATA; end end ACK4: begin if(`SCL_HIG) ack_reg <= sda; else if(`SCL_NEG)begin sda_r <= 1'b0; sda_link <= 1'b1; //0:input;1:output wait ack if(ack_reg==0 && rd_or_wr==0)begin cstate <= STOP1; end else if(ack_reg==1 && rd_or_wr==1)begin cstate <= STOP1; end else cstate <= IDLE; end else cstate <= ACK4; end STOP1: begin sda_link <= 1'b1; //0:input;1:output sda_r <= 1'b0; if(`SCL_HIG)begin iic_done <= 1'b1; cstate <= IDLE; iic_busy <= 0; end else cstate <= STOP1; end default: cstate <= IDLE; endcase end assign sda = sda_link ? sda_r:1'bz; //--------------------------------------------- endmodule
3.IIC读写模型(可以理解为从机)
IIC读写模型用于模拟EEPROM从机,用于和主机交互,一般我们写好主机程序之后,没有读写模型不能仿真,不能验证代码的正确性,这里读写模型可方便仿真。
`timescale 1ns / 1ps // // Company: // Engineer: nathanz // // Create Date: 2021/05/01 18:35:23 // Design Name: // Module Name: iic_sim_model // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // // `define SCL_POS (cnt_delay==scl_pos_cnt) //cnt=0:scl posedge `define SCL_HIG (cnt_delay==scl_hig_cnt) //cnt=1:scl high level :data stable `define SCL_NEG (cnt_delay==scl_neg_cnt) //cnt=2:scl negedge `define SCL_LOW (cnt_delay==scl_low_cnt) //cnt=3:scl low level :data change module iic_sim_model #( parameter REGADDR_WIDTH = 16 ) ( input wire clk , input wire rst_n , //active-low input wire i_scl , //为输入 inout wire io_sda ); /********************************************/ parameter clk_period_cnt = 1000 ; localparam IDLE = 4'd0 , START1 = 4'd1 , ADD1 = 4'd2 , ACK1 = 4'd3 , ADD2 = 4'd4 , ACK2 = 4'd5 , ADD2_1 = 4'd6 , ACK2_1 = 4'd7 , START2 = 4'd8 , ADD3 = 4'd9 , ACK3 = 4'd10 , DATA = 4'd11 , ACK4 = 4'd12 , STOP1 = 4'd13 ; localparam device_id = 8'ha0; reg[3:0] cstate ; // reg sda_link ; //sda direction crtl 1:output;0:input reg sda_r ; // reg[3:0] num ; // reg iic_busy ; reg[7:0] rec_device_id=0 ; reg[15:0] iic_reg_addr=0 ; reg[7:0] read_back_reg=0 ; wire[7:0] rd_data ; reg[7:0] wr_data=0 ; wire rd_or_wr ; reg[15:0] cnt_delay ; wire[15:0] scl_pos_cnt ; wire[15:0] scl_hig_cnt ; wire[15:0] scl_neg_cnt ; wire[15:0] scl_low_cnt ; wire iic_en=1 ; reg i_scl_dly1 ; wire i_scl_rise ; wire i_scl_fall ; reg io_sda_dly1 ; wire io_sda_rise ; wire io_sda_fall ; assign rd_or_wr = rec_device_id[0]; always @ (posedge clk or negedge rst_n) if(!rst_n) begin i_scl_dly1 <= 1'b0; io_sda_dly1 <= 1'b0; end else begin i_scl_dly1 <= i_scl; io_sda_dly1 <= io_sda; end //sda上升沿 assign io_sda_rise = io_sda & ~io_sda_dly1; //sda下降沿 assign io_sda_fall = ~io_sda & io_sda_dly1; //scl上升沿 assign i_scl_rise = i_scl & ~i_scl_dly1; //scl下降沿 assign i_scl_fall = ~i_scl & i_scl_dly1; assign scl_valid = iic_busy; assign scl_low_cnt = (clk_period_cnt>>2)-1; assign scl_pos_cnt = (clk_period_cnt>>1)-1; assign scl_hig_cnt = (clk_period_cnt>>1)+(clk_period_cnt>>2)-1; assign scl_neg_cnt = clk_period_cnt-1; //cnt_delay always @ (posedge clk or negedge rst_n) if( !rst_n ) cnt_delay <= 10'd0; else if(scl_valid==1 && i_scl_fall==1) cnt_delay <= 10'd0; else cnt_delay <= cnt_delay+1'b1; always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin cstate <= IDLE; sda_link <= 1'b0; sda_r <= 1'b1; num <= 4'd0; rec_device_id <= 8'd0; end else case (cstate) IDLE: begin sda_link <= 1'b0; //数据线sda为input rec_device_id <= 8'h00; if(iic_en) begin //iic_en==1,开始操作 iic_busy <= 1'b1; cstate <= START1; end else cstate <= IDLE; //没有任何键被按下 end START1: begin if(i_scl==1 && io_sda_fall==1) begin //起始信号 cstate <= ADD1; num <= 4'd0; //num计数清零 end else cstate <= START1; //等待起始信号 end ADD1: begin //设备地址 if(`SCL_NEG && num==7)begin num <= 4'd0; //num计数清零 sda_r <= 1'b1; sda_link <= 1'b0; //sda置为高阻态(input) cstate <= ACK1; end else if(`SCL_NEG)begin cstate <= ADD1; num <= num+1'b1; end else if(`SCL_HIG) begin cstate <= ADD1; case (num) 4'd0: rec_device_id[7] <= io_sda; 4'd1: rec_device_id[6] <= io_sda; 4'd2: rec_device_id[5] <= io_sda; 4'd3: rec_device_id[4] <= io_sda; 4'd4: rec_device_id[3] <= io_sda; 4'd5: rec_device_id[2] <= io_sda; 4'd6: rec_device_id[1] <= io_sda; 4'd7: rec_device_id[0] <= io_sda; // 4'd8: rd_or_wr <= io_sda; default: ; endcase end else cstate <= ADD1; end ACK1: begin sda_r <= 1'b0; //回复ACK; sda_link <= 1'b1; //sda :output if(`SCL_NEG) begin sda_r <= 1'b1; // sda_link <= 1'b0; //sda :input if(REGADDR_WIDTH == 16)begin cstate <= ADD2; end else if(REGADDR_WIDTH == 8)begin cstate <= ADD2_1; end else cstate <= ACK1; end end ADD2: begin //内存地址 if(`SCL_NEG && num==7)begin num <= 4'd0; //num计数清零 sda_r <= 1'b1; sda_link <= 1'b0; //sda置为高阻态(input) cstate <= ACK2; end else if(`SCL_NEG)begin cstate <= ADD2; num <= num+1'b1; end else if(`SCL_HIG) begin cstate <= ADD2; case (num) 4'd0: ; 4'd0: iic_reg_addr[15] <= io_sda; 4'd1: iic_reg_addr[14] <= io_sda; 4'd2: iic_reg_addr[13] <= io_sda; 4'd3: iic_reg_addr[12] <= io_sda; 4'd4: iic_reg_addr[11] <= io_sda; 4'd5: iic_reg_addr[10] <= io_sda; 4'd6: iic_reg_addr[9] <= io_sda; 4'd7: iic_reg_addr[8] <= io_sda; default: ; endcase end else cstate <= ADD2; end ACK2: begin sda_r <= 1'b0; //回复ACK; sda_link <= 1'b1; //sda输出 if(`SCL_NEG)begin sda_r <= 1'b1; //回复ACK结束; sda_link <= 1'b0; //sda输入 cstate <= ADD2_1; end end ADD2_1: begin if(`SCL_NEG && num==7)begin num <= 4'd0; //num计数清零 sda_r <= 1'b1; sda_link <= 1'b0; //sda置为高阻态(input) cstate <= ACK2_1; end else if(`SCL_NEG)begin cstate <= ADD2_1; num <= num+1'b1; end else if(`SCL_HIG) begin cstate <= ADD2_1; case (num) 4'd0: iic_reg_addr[7] <= io_sda; 4'd1: iic_reg_addr[6] <= io_sda; 4'd2: iic_reg_addr[5] <= io_sda; 4'd3: iic_reg_addr[4] <= io_sda; 4'd4: iic_reg_addr[3] <= io_sda; 4'd5: iic_reg_addr[2] <= io_sda; 4'd6: iic_reg_addr[1] <= io_sda; 4'd7: iic_reg_addr[0] <= io_sda; default: ; endcase end else cstate <= ADD2_1; end ACK2_1: begin sda_r <= 1'b0; //回复ACK; sda_link <= 1'b1; //sda :output if(`SCL_NEG)begin sda_r <= 1'b1; // sda_link <= 1'b0; //sda :input if(rd_or_wr) cstate <= START2; else cstate <= DATA; end else cstate <= ACK2_1; end START2: begin //读操作起始位 if(i_scl==1 && io_sda_fall==1) begin //起始信号 cstate <= ADD3; num <= 4'd0; //num计数清零 end else cstate <= START2; //等待起始信号 end ADD3: begin //接收读操作地址 if(`SCL_NEG && num==8)begin num <= 4'd0; //num计数清零 sda_r <= 1'b1; sda_link <= 1'b0; //sda置为高阻态(input) cstate <= ACK3; end else if(`SCL_NEG)begin cstate <= ADD3; num <= num+1'b1; end else if(`SCL_HIG) begin case (num) 4'd0: ; 4'd1: rec_device_id[7] <= io_sda; 4'd2: rec_device_id[6] <= io_sda; 4'd3: rec_device_id[5] <= io_sda; 4'd4: rec_device_id[4] <= io_sda; 4'd5: rec_device_id[3] <= io_sda; 4'd6: rec_device_id[2] <= io_sda; 4'd7: rec_device_id[1] <= io_sda; 4'd8: rec_device_id[0] <= io_sda; default: ; endcase cstate <= ADD3; end else cstate <= ADD3; end ACK3: begin sda_r <= 1'b0; //回复ACK; sda_link <= 1'b1; //sda:output if(`SCL_NEG)begin if(rd_or_wr==0) sda_link <= 1'b0; //sda:intput else sda_link <= 1'b1; //sda:intput sda_r <= 1'b1; //回复ACK结束; cstate <= DATA; end // if(i_scl==1 && io_sda_rise==1) begin // cstate <= DATA; // end end DATA: begin if(rd_or_wr) begin //读操作 输出 sda_link <= 1'b1; //sda:output if(`SCL_NEG && num==7)begin num <= 4'd0; //num计数清零 sda_r <= 1'b1; sda_link <= 1'b0; //sda 0:intput 1:outpu cstate <= ACK4; end else if(`SCL_NEG)begin cstate <= DATA; num <= num+1'b1; end else if(`SCL_LOW) begin case (num) 4'd0: sda_r <= read_back_reg[7]; 4'd1: sda_r <= read_back_reg[6]; 4'd2: sda_r <= read_back_reg[5]; 4'd3: sda_r <= read_back_reg[4]; 4'd4: sda_r <= read_back_reg[3]; 4'd5: sda_r <= read_back_reg[2]; 4'd6: sda_r <= read_back_reg[1]; 4'd7: sda_r <= read_back_reg[0]; default: ; endcase cstate <= DATA; end end else begin //写操作 输入 sda_link <= 1'b0; if(`SCL_NEG && num==7)begin num <= 4'd0; //num计数清零 sda_r <= 1'b1; sda_link <= 1'b0; //sda置为高阻态(input) cstate <= ACK4; end else if(`SCL_NEG)begin cstate <= DATA; num <= num+1'b1; end else if(`SCL_HIG) begin case (num) 4'd0: wr_data[7] <= io_sda; 4'd1: wr_data[6] <= io_sda; 4'd2: wr_data[5] <= io_sda; 4'd3: wr_data[4] <= io_sda; 4'd4: wr_data[3] <= io_sda; 4'd5: wr_data[2] <= io_sda; 4'd6: wr_data[1] <= io_sda; 4'd7: wr_data[0] <= io_sda; default: ; endcase cstate <= DATA; end end end ACK4: begin if(rd_or_wr)begin sda_r <= 1'b1; //回复ACK; sda_link <= 1'b1; //sda output end else begin sda_r <= 1'b0; //回复 NOACK; sda_link <= 1'b1; //sda output end if(`SCL_NEG)begin sda_r <= 1'b1; //回复ACK结束; sda_link <= 1'b0; //sda input cstate <= STOP1; end end STOP1: begin if(io_sda_rise==1) begin iic_busy <= 1'b0; cstate <= IDLE; end end default: cstate <= IDLE; endcase end assign io_sda = sda_link ? sda_r:1'bz; reg wr_en ; reg rd_en ; always @ (posedge clk or negedge rst_n) if(!rst_n) rd_en <= 0; else if(cstate==DATA && rd_or_wr) rd_en <= 1; else rd_en <= 0; always @ (posedge clk or negedge rst_n) if(!rst_n) wr_en <= 0; else if(cstate==ACK4 && rd_or_wr==0) wr_en <= 1; else wr_en <= 0; always @ (posedge clk or negedge rst_n) if(!rst_n) read_back_reg <= 0; else read_back_reg <= rd_data; AT24C512B #( .REGADDR_WIDTH (16 ), .DATA_WIDTH (8 ) ) AT24C512B_inst ( .clk (clk ), // .rst_n (rst_n ), //active-low .iic_reg_addr (iic_reg_addr ), .wr_data_en (wr_en ), .wr_data (wr_data ), .rd_en (rd_en ), .rd_data_en (rd_data_en ), .rd_data (rd_data ) ); endmodule
4.AT24C512B模型
AT24C512B模型用于模拟AT24C512B EEPROM存储空间读写接口,init_memory.txt可以用于第一次仿真读写时初始化(模拟EEPROM器件初始值为0XFF),我们仿真时可以使用读写函数从memory.txt文件读写。
//***************************************************************************** // // Author : NAZ // Version : 1.0 // Filename : AT24C512B.v // Date Last Modified : $Date: 2021/12/08 $ // Date Created : $Date: 2021/11/04 $ // Design Name : AT24C512B // // Describe : // This module is used for simulation,when IIC read data ,Read the data in memory.txt // when when IIC write data,write the data to memory.txt // //***************************************************************************** `include "macro_define.v" `timescale 1ns/1ns module AT24C512B #( parameter REGADDR_WIDTH = 16, parameter DATA_WIDTH = 8 ) ( input wire clk , // input wire rst_n , //active-low input wire[REGADDR_WIDTH-1:0] iic_reg_addr, input wire wr_data_en , input wire[7:0] wr_data , input wire rd_en , output wire rd_data_en , output reg[7:0] rd_data=0 ); /******************reg/wire********************/ reg [DATA_WIDTH-1:0] register[2**REGADDR_WIDTH-1:0]; // init memory, AT24C512B initial values are 0xff `ifdef FIRST_SIM initial begin $readmemh("../sim/init_memory.txt",register); end `else initial begin $readmemh("../src/memory.txt",register); end `endif //write the register always @(posedge clk) if(wr_data_en) register[iic_reg_addr] <= wr_data; //write the register values to memory.txt always@(posedge clk) if(wr_data_en) $writememh("../src/memory.txt",register); //when iic read data always @(posedge clk)begin if(rd_en) rd_data <= register[iic_reg_addr]; end endmodule
5.初始化文件和读写文件
init_memory.txt文件为初始化文件(内容为按一定格式的0XFF文件),memory.txt文件为读写文件(为仿真后生成文件)。如有需要可联系博主(992716773@qq
.com)。
6.仿真顶层文件
仿真顶层文件可以用于读写具体的地址,将IIC读写方式使用task方式写为函数的方式,方便不同地址读写,并且在读写时打印读写信息。
//***************************************************************************** // // Author : NAZ // Version : 1.0 // Filename : iic_master_tb.v // Date Last Modified : $Date: 2021/12/08 $ // Date Created : $Date: 2021/10/05 $ // Design Name : iic_master_tb // // Describe : // This module is used for IIC simulation excitation // //*****************************************************************************" `timescale 1ns/1ns `include "macro_define.v" module iic_master_tb(); reg clk ; reg rst_n ; // 与控制器通信信号 reg iic_en ; reg rd_or_wr ; reg[6:0] device_addr=7'h50 ; reg[15:0] iic_reg_addr ; reg[7:0] iic_reg_data ; wire[7:0] read_data ; wire iic_done ; wire iic_busy ; // 外部信号 wire scl ; wire sda ; //*************************************************** // testcase // 时钟 always begin #10 clk = ~clk; end // 初始化 initial begin clk = 0; rst_n = 0; iic_en <= 1'b0; rd_or_wr <= 0; #100; rst_n = 1; #1000; iic_write(7'h50,16'h0000,32'hAA); #100 iic_read(7'h50,16'h0002); #100 iic_write(7'h50,16'h0001,32'h01); #100 iic_write(7'h50,16'h0002,32'h02); #100 iic_write(7'h50,16'h0003,32'h03); #100 iic_write(7'h50,16'h0004,32'h55); #100 iic_read(7'h50,16'h0004); #100 iic_read(7'h50,16'h1000); #100 iic_read(7'h50,16'h1234); $stop; end task iic_write( input [6:0] iic_device_id, input [15:0] iic_addr, input [31:0] iic_data ); begin rd_or_wr = 1'b0; device_addr = iic_device_id; iic_reg_addr = iic_addr; iic_reg_data = iic_data; #100 iic_en <= 1; #100 iic_en = 0; #1400000; $display("current simulation time is %t,write addr is 16'h%h,write data is 8'h%h.",$time,iic_reg_addr,iic_reg_data); end endtask task iic_read( input [6:0] iic_device_id, input [15:0] iic_addr ); begin rd_or_wr = 1'b1; device_addr = iic_device_id; iic_reg_addr = iic_addr; #100 iic_en <= 1; #100 iic_en = 0; #1400000; $display("current simulation time is %t,read addr is 16'h%h,read data is 8'h%h.",$time,iic_reg_addr,read_data); end endtask //*************************************************** // 例化顶层文件 iic_master #( .IIC_RATE ("100k" ), //50k,100k or 400k .REGADDR_WIDTH (16 ) ) iic_master_inst ( .clk (clk ), //50MHz主时钟 .rst_n (rst_n ), //复位信号 .scl (scl ), //IIC时钟 .sda (sda ), //IIC数据 .iic_start (iic_en ), //IIC使能 .rd_or_wr (rd_or_wr ), //读写标志:读1,写0 .iic_device_id (device_addr ), //IIC器件地址 .iic_reg_addr (iic_reg_addr ), //寄存器地址 .iic_wr_data (iic_reg_data ), //寄存器数据 .iic_rd_data_en (), .iic_rd_data (read_data ), //接收数据 .iic_done (iic_done ), //IIC完成标志 .iic_busy (iic_busy ) //IIC忙 ); `ifdef SIM_MODEL iic_sim_model #( .REGADDR_WIDTH(16) ) iic_sim_model_inst ( .clk (clk ), //50Mhz .rst_n (rst_n ), //active-low .i_scl (scl ), //为输入 .io_sda (sda ) ); `endif endmodule
在初始化阶段,初始化函数初始化AT24C512B内存储空间为全0XFF。
继续运行仿真,可以看到打印信息,写入和读取的值,以读取地址0x0002为例,在未向该地址写入时,返回的读取值为FF(初始值),在写入0x0004后再读取该地址,返回读取值与写入一致,其他地址可以自己定义验证。对应的memory地址空间在写入值后,内存空间随之改变。对应的memory.txt文件随着写入的值同时更新。
若有相关问题可以互相讨论
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。