FPGA——IIC状态机实现及仿真

一、设计思路

二、IIC代码

module iic_module(
   rst_n       , 
   clk         ,
   waddr_num   ,      //选择两字节地址或单字节地址
   device_addr ,
   word_addr   ,
   wr          ,
   wr_data     ,
   wr_data_vld ,
   rd          ,
   rd_data     ,
   rd_data_vld ,
   done        ,
   scl         ,
   sda
);
//输入输出参数
parameter SYS_CLOCK  =  50_000_000;
parameter SCL_CLOCK  =  380_000;    //快速模式
parameter WADDR_W    =  2;
parameter DADDR_W    =  3;
parameter WR_ADDR_W  =  16;
parameter WDATA_W    =  8;
parameter RDATA_W    =  8;

//计数器参数
parameter SCL_N      =  SYS_CLOCK/SCL_CLOCK;
parameter SCL_W      =  8;

parameter HABIT_W    =  5;
parameter HABIT_N    =  18;

parameter BYTE_W     =  6;
parameter BYTEN_W    =  2;

//状态机参数
parameter IDLE       =  9'b000000001;  //空闲
parameter S1         =  9'b000000010;  //写开始
parameter S2         =  9'b000000100;  //写控制
parameter S3         =  9'b000001000;  //读开始
parameter S4         =  9'b000010000;  //读控制
parameter S5         =  9'b000100000;  //读数据
parameter S6         =  9'b001000000;  //写地址
parameter S7         =  9'b010000000;  //写数据
parameter S8         =  9'b100000000;  //结束
parameter STATE_W    =  9;

//中间变量参数
parameter SDA_DATA_N =  8;

input                      rst_n; 
input                      clk;      
input    [WR_ADDR_W-1:0]   word_addr;   
input                      wr;          
input    [WDATA_W-1:0]     wr_data;     
input                      rd;          
input    [WADDR_W-1:0]     waddr_num;
input    [DADDR_W-1:0]     device_addr;

output                     wr_data_vld; 
output   [RDATA_W-1:0]     rd_data;  
output                     rd_data_vld; 
output                     done;
output                     scl;
inout                      sda;

wire                       wr_data_vld; 
reg      [RDATA_W-1:0]     rd_data;  
wire                       rd_data_vld; 
reg                        done;
reg                        scl;
wire                       sda;

//计数器变量
reg      [SCL_W-1:0]       cnt_scl;
wire                       add_cnt_scl;
wire                       end_cnt_scl;
reg                        scl_vld;

reg      [HABIT_W-1:0]     cnt_hlbit;
wire                       add_cnt_hlbit;
wire                       end_cnt_hlbit;

reg      [BYTE_W-1:0]      cnt_bytes;
wire                       add_cnt_bytes;
wire                       end_cnt_bytes;
reg      [BYTEN_W-1:0]     bytes_num;

//中间变量
reg                        scl_high;
reg                        scl_low;
reg      [SDA_DATA_N-1:0]  sda_out_data;
reg                        wr_flag;
reg                        rd_flag;
reg                        ack_flag;
reg                        noack_flag;
reg                        r_begin;
reg                        ack_error;

wire                       sda_in;
reg                        sda_en;
reg                        sda_out;

//状态机变量
reg      [STATE_W-1:0]     state_c;
reg      [STATE_W-1:0]     state_n;
wire                       idle2s1_start;
wire                       s12s2_start;
wire                       s22s6_start;
wire                       s22idle_start;
wire                       s32s4_start;
wire                       s42s5_start;
wire                       s42idle_start;
wire                       s52s8_start;
wire                       s62s7_start;
wire                       s62s3_start;
wire                       s62idle_start;
wire                       s72s8_start;
wire                       s72idle_start;
wire                       s82idle_start;


// assign sda     = sda_en ? sda_out : 1'bz;
assign sda = (sda_en && !sda_out) ? 1'b0 : 1'bz;
assign sda_in  = sda;
//SDA接上拉电阻
pullup(sda);

//SCL时钟计数器
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      cnt_scl <= 0;
   else if(add_cnt_scl)begin
      if(end_cnt_scl)
         cnt_scl <= 0;
      else
         cnt_scl <= cnt_scl + 1'b1;
   end
end
assign add_cnt_scl = scl_vld;
assign end_cnt_scl = (add_cnt_scl && cnt_scl == SCL_N - 1) || ack_error;

//高低电平计数器
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)begin
      cnt_hlbit <= 0;
   end
   else if(add_cnt_hlbit)begin
      if(end_cnt_hlbit)
         cnt_hlbit <= 0;
      else
         cnt_hlbit <= cnt_hlbit + 1'b1;
   end
end
assign add_cnt_hlbit = (state_c == S2 || state_c == S6 || state_c == S7 || state_c == S4 || state_c == S5) && (scl_low || scl_high);
assign end_cnt_hlbit = (add_cnt_hlbit && cnt_hlbit == HABIT_N - 1) || ack_error;

//字节数计数器
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)begin
      cnt_bytes <= 0;
   end
   else if(add_cnt_bytes)begin
      if(end_cnt_bytes)
         cnt_bytes <= 0;
      else
         cnt_bytes <= cnt_bytes + 1'b1;
      end
end
assign add_cnt_bytes = end_cnt_hlbit && state_c == S6;
assign end_cnt_bytes = add_cnt_bytes && cnt_bytes == bytes_num - 1 ;

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      bytes_num <= 1;
   else if((wr || rd) && !(wr_flag ||rd_flag))
      bytes_num <= waddr_num;
end

//scl 时钟高电平中部标志位
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      scl_high <= 0;
   else if(cnt_scl == (SCL_N>>2)-1 && add_cnt_scl)
      scl_high <= 1;
   else
      scl_high <= 0;
end
//scl 时钟低电平中部标志位
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      scl_low <= 0;
   else if(cnt_scl == (SCL_N>>2) + (SCL_N>>1) -1 && add_cnt_scl)
      scl_low <= 1;
   else
      scl_low <= 0;
end



always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      scl_vld <= 0;
   else if(wr || rd)
      scl_vld <= 1;
   else if(done)
      scl_vld <= 0;
   else if(ack_error)
      scl_vld <= 0;
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      scl <= 1;
   else if(cnt_scl == (SCL_N>>1)-1 && add_cnt_scl && state_c != S8 && state_c != S1)
      scl <= 0;
   else if(state_c == S1)begin
      if(cnt_scl == (SCL_N>>1) -1 && add_cnt_scl && sda == 0)
         scl <= 0;
   end
   else if(end_cnt_scl)
      scl <= 1;
end


//状态机
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      state_c <= IDLE;
   else
      state_c <= state_n;
end

always @(*)begin
   case(state_c)
      IDLE:begin
         if(idle2s1_start)
            state_n = S1;
         else
            state_n = state_c;
      end
      S1:begin
         if(s12s2_start)
            state_n = S2;
         else
            state_n = state_c;
      end
      S2:begin
         if(s22idle_start)
            state_n = IDLE;
         else if(s22s6_start)
            state_n = S6;
         else
            state_n = state_c;
      end
      S3:begin
         if(s32s4_start)
            state_n = S4;
         else
            state_n = state_c;
      end
      S4:begin
         if(s42idle_start)
            state_n = IDLE;
         else if(s42s5_start)
            state_n = S5;
         else
            state_n = state_c;
      end
      S5:begin
         if(s52s8_start)
            state_n = S8;
         else
            state_n = state_c;
      end
      S6:begin
         if(s62idle_start)
            state_n = IDLE;
         else if(s62s7_start)
            state_n = S7;
         else if(s62s3_start)
            state_n = S3;
         else
            state_n = state_c;
      end
      S7:begin
         if(s72idle_start)
            state_n = IDLE;
         else if(s72s8_start)
            state_n = S8;
         else
            state_n = state_c;
      end
      S8:begin
         if(s82idle_start)
            state_n = IDLE;
         else
            state_n = state_c;
      end
      default:state_n = IDLE;
   endcase
end

assign idle2s1_start =  state_c == IDLE && (wr || rd);
assign s12s2_start   =  state_c == S1   && scl_low && !sda && !scl;
assign s22s6_start   =  state_c == S2   && ack_flag && scl_low;
assign s22idle_start =  state_c == S2   && !ack_flag && scl_low && !sda_en;
assign s32s4_start   =  state_c == S3   && scl_low && !sda && !scl;
assign s42s5_start   =  state_c == S4   && ack_flag && scl_low;
assign s42idle_start =  state_c == S4   && !ack_flag && scl_low && !sda_en;
assign s52s8_start   =  state_c == S5   && noack_flag && scl_low;
assign s62idle_start =  state_c == S6   && end_cnt_bytes && !ack_flag;
assign s62s7_start   =  state_c == S6   && end_cnt_bytes && ack_flag && wr_flag;
assign s62s3_start   =  state_c == S6   && end_cnt_bytes && ack_flag && rd_flag;
assign s72s8_start   =  state_c == S7   && ack_flag && scl_low;
assign s72idle_start =  state_c == S7   && !ack_flag && scl_low && !sda_en;
assign s82idle_start =  state_c == S8   && end_cnt_scl && sda && scl;

//SDA三态门使能
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      sda_en <= 0;
   else if(state_c == IDLE)begin
      if(wr || rd)
         sda_en <= 1;
      else
         sda_en <= 0;
   end
   else if(state_c == S2 || state_c == S6 || state_c == S7 || state_c == S4)begin
      if(cnt_hlbit == 16 - 1 && add_cnt_hlbit)
         sda_en <= 0;
      //读控制接收ACK后不必拉高,因为之后是读数据,使能要一直拉低
      else if(end_cnt_hlbit && state_c != S4)
         sda_en <= 1;
   end
   else if(state_c == S5)begin
      if(cnt_hlbit >= 0 && cnt_hlbit < 16 - 1)
         sda_en <= 0;
      else if(cnt_hlbit >= 17 - 1 && cnt_hlbit < 18)
         sda_en <= 1;
   end
end

//SDA输出
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)begin
      sda_out <= 1;
   end
   //写起始位
   else if(state_c == S1)begin
      if(end_cnt_scl && sda == 1)
         sda_out <= 0;
   end
   //写控制
   else if(state_c == S2)begin
      sda_out <= sda_out_data[7];
   end
   //读起始位
   else if(state_c == S3)begin
         //使用r_begin是为了,在S3状态初始的时候就是高电平,便于拉低
         sda_out <= r_begin;
   end
   //读控制
   else if(state_c == S4)begin
      sda_out <= sda_out_data[7];
   end
   //发送NOACK
   else if(state_c == S5)begin
      if(cnt_hlbit == 16 - 1 && add_cnt_hlbit)
         sda_out <= 1;
      else if(cnt_hlbit == 18 - 1 && add_cnt_hlbit)
         sda_out <= 0;
   end
   //写地址
   else if(state_c == S6)begin
      sda_out <= sda_out_data[7];
   end
   //写数据
   else if(state_c == S7)begin
      sda_out <= sda_out_data[7];
   end
   else if(state_c == S8)begin
      if(cnt_scl == (SCL_N >> 1) - 1 && add_cnt_scl)
         sda_out <= 1;
   end
end

//SDA接收数据
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      rd_data <= 0;
   else if(state_c == S5)begin
      if(cnt_hlbit < 17 - 1 && cnt_hlbit >= 0 && scl_high)
         rd_data <= {rd_data[6:0],sda_in};
   end
end

//SDA输出数据
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      sda_out_data <= 0;
   else if(state_c == S1)
      sda_out_data <= {4'b1010,device_addr,1'b0};  //写控制
   else if(state_c == S2)begin
      if(scl_low && cnt_hlbit < 17 - 1 && cnt_hlbit >= 0)
         sda_out_data <= {sda_out_data[6:0],1'b0};
      else if(scl_low && end_cnt_hlbit)begin
         if(bytes_num == 1)
            sda_out_data <= word_addr[7:0];
         else
            sda_out_data <= word_addr[15:8];
      end
   end
   else if(state_c == S3)
      sda_out_data <= {4'b1010,device_addr,1'b1};  //读控制
   else if(state_c == S4)begin
      if(scl_low && cnt_hlbit < 17 - 1 && cnt_hlbit >= 0)
         sda_out_data <= {sda_out_data[6:0],1'b0};
   end
   else if(state_c == S6)begin
      if(scl_low && cnt_hlbit < 17 - 1 && cnt_hlbit >= 0)
         sda_out_data <= {sda_out_data[6:0],1'b0};
      else if(add_cnt_bytes && !end_cnt_bytes)
         sda_out_data <= word_addr[7:0];
      else if(end_cnt_bytes)
         sda_out_data  <= wr_data;
   end
   else if(state_c == S7)begin
      if(scl_low && cnt_hlbit < 17 - 1 && cnt_hlbit >= 0)
         sda_out_data <= {sda_out_data[6:0],1'b0};
   end
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      done <= 0;
   else if(state_c == S8)begin
      //提前一拍与SCL计数器结束条件对齐
      if(cnt_scl == SCL_N - 2 && add_cnt_scl && scl && sda)
         done <= 1;
      else
         done <= 0;
   end
end

//读有效
assign wr_data_vld = state_c == S2 || state_c == S6 || state_c == S7 || state_c == S4;
assign rd_data_vld = state_c == S5;

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      wr_flag <= 0;
   else if(wr && !(wr_flag || rd_flag))
      wr_flag <= 1;
   else if(done)
      wr_flag <= 0;
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      rd_flag <= 0;
   else if(rd && !(wr_flag || rd_flag))
      rd_flag <= 1;
   else if(done)
      rd_flag <= 0;
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      ack_flag <= 0;
   else if(scl_high && cnt_hlbit == 17 - 1 && add_cnt_hlbit && sda == 0)
      ack_flag <= 1;
   else if(scl_low && end_cnt_hlbit)
      ack_flag <= 0;
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      noack_flag <= 0;
   else if(state_c == S5)begin
      if(scl_high && cnt_hlbit == 17 - 1 && add_cnt_hlbit && sda == 1)
         noack_flag <= 1;
      else if(scl_low && end_cnt_hlbit)
         noack_flag <= 0;
   end
end

//读起始位控制信号
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      r_begin <= 1;
   else if(state_c == S3 && scl_high)begin
      r_begin <= 0;
   end
   else if(state_c == S3 && scl_low)begin
      r_begin <= 1;
   end
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      ack_error <= 0;
   else if(s22idle_start || s62idle_start || s72idle_start || s42idle_start)
      ack_error <= 1;
   else
      ack_error <= 0;
end

endmodule

三、仿真文件

`timescale 1ns / 1ns
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2021/02/03 19:53:42
// Design Name: 
// Module Name: iic_module_tb
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module iic_module_tb();

parameter SYS_CLOCK  =  50_000_000;
parameter SCL_CLOCK  =  380_000;    //快速模式
parameter RD_DATA_W  =  6;
parameter WR_DATA_W  =  6;
parameter WADDR_W    =  2;
parameter DADDR_W    =  3;
parameter WR_ADDR_W  =  16;
parameter WDATA_W    =  8;
parameter RDATA_W    =  8;
parameter SCL_N      =  SYS_CLOCK/SCL_CLOCK;
parameter SCL_W      =  7;
parameter DATA_N     =  10;
parameter DATA_W     =  4;
parameter BYTE_W     =  6;
parameter CTR_W      =  8;


reg                      rst_n; 
reg                      clk;      
reg    [WR_ADDR_W-1:0]   word_addr;   
reg                      wr;          
reg    [WDATA_W-1:0]     wr_data;     
reg                      rd;          

wire                     wr_data_vld; 
wire   [RDATA_W-1:0]     rd_data;  
wire                     rd_data_vld; 
wire                     done;
wire                     scl;
wire                     sda;

iic_module iic_module(
   .rst_n       (rst_n),
   .clk         (clk),
   .waddr_num   (3'd2),
   .device_addr (3'b000),
   .word_addr   (word_addr),
   .wr          (wr),
   .wr_data     (wr_data),
   .wr_data_vld (wr_data_vld),
   .rd          (rd),
   .rd_data     (rd_data),
   .rd_data_vld (rd_data_vld),
   .done        (done),
   .scl         (scl),
   .sda         (sda)
);

M24LC64 M24LC64(
	.A0(1'b0),
	.A1(1'b0),
	.A2(1'b0),
	.WP(1'b0),
	.SDA(sda),
	.SCL(scl),
	.RESET(!rst_n)
);

//时钟周期,单位ns,在这里修改时钟周期
parameter CYCLE = 20;

//复位时间,此时表示复位3个时钟周期的时间
parameter RST_TIME = 3;

//生成本地时钟50M
initial begin
   clk = 0;
   forever
   #(CYCLE/2)
   clk=~clk;
end

//产生复位信号
initial begin
   $display(M24LC64.RdDataByte);
   $display(M24LC64.WrDataByte);
   rst_n = 1;
   #2;
   rst_n = 0;
   #(CYCLE * RST_TIME);
   rst_n = 1;
end

initial begin
   rd = 0;
   @ (posedge rst_n)
   #(10*CYCLE)
   word_addr  = 0;
   wr_data    = 8'b1000_0001;
   repeat(5)begin
      wr      = 1'b1;
      #(CYCLE);
      wr      = 1'b0;
      #102450;
      word_addr = word_addr + 2;
      wr_data = wr_data + 2;
   end
   
   #200000;
   
   word_addr = 0;	
   repeat(5)begin
      rd      = 1'b1;
      #(CYCLE);
      rd      = 1'b0;
      #130400;
      word_addr = word_addr + 2;
   end

   #200000;
   $stop;
end

endmodule

四、使用M24LC64 EEPROM仿真模型及碰到的问题

https://www.cnblogs.com/cnlntr/p/14258703.html

  • 问题一、24LC64报错

    问题原因:波形宽度错误,不符合24LC64的时序,第一行错误的意思是 第5450ns到6710ns脉冲的宽度不符合24LC64时序,脉冲至少要1300ns的宽度
    解决办法:将SCL时钟计数器频率调小一点
parameter SCL_CLOCK  =  380_000;    //快速模式
  • 问题二、一次读时序之后,第二次的读写时序收不到ACK

    原因:仿真模型的内部信号writeactive没有拉低

将仿真模型里面的写数据间隔改成TWC改成500就可以解决问题了

五、仿真波形

posted @ 2021-02-05 17:29  AdriftCore  阅读(1976)  评论(3编辑  收藏  举报