• 二线制I2C CMOS串行EEPROM续


    本篇文章主要介绍一下EEPROM读写器件的设计思路,以及未来测试此器件搭建的测试平台。

    1、串行EEPROM读写器件

    我们要设计一个串行EEPROM读写器件,这要求我们设计出能够综合的Verilog HDL代码。所谓串行EEPROM读写器件就是指将我们平常输入信号的输入习惯产生符合I2C串行总线的数据。我们给此模块命名为EEPROM_WR。
    要搭建测试平台才能够对设计进行验证,这里搭建的测试平台,只用做行为级描述,不一定符合可综合的设计要求。搭建测试平台需要信号输入模块signal和EEPROM模块。
    EEPROM读写电路及其测试电路.png
    下面就这三个模块做一一的详细介绍

    EEPROM

    我们理想的EEPROM数据的读写规则就是按照I2C总线给出的规则进行。在这里,我们先贴出相应的代码,然后进行分析。

    `timescale 1ns/1ns
    `define timeslice 100
     module EEPROM(scl,sda);
     input scl;
     inout sda;
    
     reg out_flag;
     reg [7:0] memory [2047:0];
     reg [10:0] address;
     reg [7:0] memory_buf;
     reg [7:0] sda_buf;
     reg [7:0] shift;
     reg [7:0] addr_byte;
     reg [7:0] ctrl_byte;
     reg [1:0] state;
     integer i;
    
      parameter r7=8'b10101111, w7=8'b10101110,
            r6=8'b10101101, w6=8'b10101100,
            r5=8'b10101011, w5=8'b10101010,
            r4=8'b10101001, w4=8'b10101000,
            r3=8'b10100111, w3=8'b10100110,
            r2=8'b10100101, w2=8'b10100100,
            r1=8'b10100011, w1=8'b10100010,
            r0=8'b10100001, w0=8'b10100000;
    
      assign sda=(out_flag==1)? sda_buf[7]:1'bz; //sda线上作为输出
    
    initial //初始化
    begin
      addr_byte=0;
      ctrl_byte=0;
      out_flag=0;
      sda_buf=0;
      state=2'b00;
      memory_buf=0;
      address=0;
      shift=0;
      for(i=0;i<=2047;i=i+1)
      memory[i]=0;
    end
    
     always @(negedge sda)  //scl为高电平,sda的下降沿,表示启动信号
        begin
        if(scl==1)
         begin
          state=state+1;
          if(state==2'b11)   //读操作的另一次启动信号,此时已经完成了从sda总线上读控制字和读地址的操作。
          disable write_to_EEPROM;
      end
     end
    
      always@(posedge sda) //仅仅跟随在启动信号之后的触发条件,因为控制字是1010XXXX,所以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;    //从sda上读取写控制字和地址
                  write_to_EEPROM;  //将sda上的数据写如EEPROM.
                end
              else
                state=2'b00;
              end
         2'b11: read_from_EEPROM; // 写控制字,读数据
         default: state=2'b00;
       endcase
     end
    end
    
    task shift_in; //总共消耗9个scl周期,前8个用于读数据,将sda读出,最后一个用于应答位的产生,sda被驱动赋值。
      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) //最后这两个scl的下降沿是输出sda,用于应答信号,详细可参考波形1.
       begin
        #`timeslice
        out_flag=1;
        sda_buf=0; // 应答位0.
        end
     @(negedge scl) 
       begin
        #`timeslice
         out_flag=0;
        end 
     end
      endtask
    
     task read_in;
     begin
       shift_in(ctrl_byte);
       shift_in(addr_byte);
     end
     endtask
    
     task shift_out;//将sda_buf上的数据写到总线上去,共消耗9个scl时钟周期,其中前8个时钟周期是将sdf_buf的内容赋值到sda中去。
     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
     
    task stop_W_R;
      begin
        state=2'b00;
        addr_byte=0;
        ctrl_byte=0;
        out_flag=0;
        sda_buf=0;
      end
    endtask
      
    
    task write_to_EEPROM;  //将sda上的数据写入到EEPROM指定的地址中。
        begin
        shift_in(memory_buf);
        address={ctrl_byte[3:1],addr_byte};
        memory[address]=memory_buf;
        $display("EEPROM-----memory[%0h]=%0h",address,memory[address]);
        state=2'b00;
        end
    endtask
    
    task read_from_EEPROM; //将EEPROM中指定地址的值放入sda总线上。
      begin
        shift_in(ctrl_byte);
        if(ctrl_byte==r7||ctrl_byte==r6||ctrl_byte==r5||ctrl_byte==r4||ctrl_byte==r3||ctrl_byte==r2||ctrl_byte==r1||ctrl_byte==r0)
          begin
            address={ctrl_byte[3:1],addr_byte};
            sda_buf=memory[address];
            shift_out;
            state=2'b00;
          end
        end
     endtask
    endmodule
    

    写操作.png
    读操作.png

    EEPROM_WR

    EEPROM读写器件我们要求设计成可综合的设计风格代码,它接受来自信号源模型产生的读信号、写信号、并行地址信号和并行数据信号,并把它们转换成相应的串行信号发送到EEPROM的行为模型中去。
    这部分主要由两部分组成:一部分是开关组合电路,另一部分是控制时序电路。
    EEPROM读写器的结构.png
    电路上的同步采用有限状态机的设计方法实现,程序上则采用的是一个有限状态机的嵌套结构,由主状态机和从状态机通过由控制总线启动的总线在不同的输入信号下构成不同功能的较为复杂的有限状态机,这个有限状态机只有唯一的驱动时钟clk。EEPROM读写器的状态机.png
    写状态由5个状态完成,读状态由7个状态完成。在本代码中,我们选择了利用独热码对状态机进行编码,若改变状态编码,只需要改变程序中的parameter定义即可。
    下面就代码结合波形,我给大家做一下简要的分析。
    本模块以状态转移为框架,读写过程分为三步开始、读/写、结束。在读写过程中,涉及到串联转并联、并联转串联的任务。能够清楚地掌握何时进行开关的打开和关闭是设计成功的关键步骤。

    module EEPROM_WR(
                 sda,
                 scl,
                 ack,
                 reset,
                 clk,
                 wr,
                 rd,
                 addr,
                 data
                 );
    input clk,reset,wr,rd;
    input[10:0]addr;
    output scl,ack;
    inout sda;
    inout[7:0]data;
    
     reg scl;
     reg ack;
     reg wf,rf;
     reg ack_f;
     reg[1:0] head_buf;
     reg[1:0] stop_buf;
     reg[7:0] storage_buf;
     reg[8:0] w_state;
     reg[9:0] r_state;
     reg[2:0] head_state;
     reg[2:0] stop_state;
     reg[10:0] main_state;
     reg[7:0] data_from_sda;
     reg link_sda,link_read,link_write,link_head,link_stop;
     wire sda1,sda2,sda3,sda4;
    
      assign sda1=(link_head==1)? head_buf[1] : 1'b0;  //开始
      assign sda2=(link_stop==1)? stop_buf[1] : 1'b0;  //结束
      assign sda3=(link_write==1)? storage_buf[7] : 1'b0;//数据的输出
      assign sda4=sda1|sda2|sda3; //sda线上的输出数据
      assign sda=(link_sda==1)? sda4 : 1'bz;
      assign data=(link_read==1'b1)? data_from_sda : 8'hzz;
    
     parameter idle        = 11'b00000000001,   //主状态机
            ready       = 11'b00000000010,
            write_start = 11'b00000000100,
            ctrl_write  = 11'b00000001000,
            addr_write  = 11'b00000010000,
            data_write  = 11'b00000100000,
            read_start  = 11'b00001000000,
            ctrl_read   = 11'b00010000000,
            data_read   = 11'b00100000000,
            stop        = 11'b01000000000,
            ackn        = 11'b10000000000;
            
     parameter data_to_sda_7  = 9'b000000001,   //并联转串联
            data_to_sda_6  = 9'b000000010,
            data_to_sda_5  = 9'b000000100,
            data_to_sda_4  = 9'b000001000,
            data_to_sda_3  = 9'b000010000,
            data_to_sda_2  = 9'b000100000,
            data_to_sda_1  = 9'b001000000,
            data_to_sda_0  = 9'b010000000,
            data_to_sda_end = 9'b100000000;
            
    parameter sda_to_data_begin = 10'b0000000001,   //串联转并联
           sda_to_data_7     = 10'b0000000010,
           sda_to_data_6     = 10'b0000000100,
           sda_to_data_5     = 10'b0000001000,
           sda_to_data_4     = 10'b0000010000,
           sda_to_data_3     = 10'b0000100000,
           sda_to_data_2     = 10'b0001000000,
           sda_to_data_1     = 10'b0010000000,
           sda_to_data_0     = 10'b0100000000,
           sda_to_data_end   = 10'b1000000000; 
           
    parameter head_begin = 3'b001,   //开始状态
            head_bit   = 3'b010,
            head_end   = 3'b100;
            
     parameter stop_begin = 3'b001,   // 结束状态
            stop_bit   = 3'b010,
            stop_end   = 3'b100;
            
    
    task serial_to_perallel;                          
    begin
      casex(r_state)
        sda_to_data_begin: begin
                           r_state <= sda_to_data_7;
                           link_sda<=1'b0;
                           end
        sda_to_data_7    : if(scl)
                            begin
                              data_from_sda[7]<=sda;
                              r_state<=sda_to_data_6;
                            end
                           else
                              r_state<=sda_to_data_7;
        sda_to_data_6    : if(scl)
                            begin
                              data_from_sda[6]<=sda;
                              r_state<=sda_to_data_5;
                            end
                           else
                              r_state<=sda_to_data_6;
        sda_to_data_5    : if(scl)
                            begin
                              data_from_sda[5]<=sda;
                              r_state<=sda_to_data_4;
                            end
                           else
                              r_state<=sda_to_data_5;
        sda_to_data_4    : if(scl)
                            begin
                              data_from_sda[4]<=sda;
                              r_state<=sda_to_data_3;
                            end
                           else
                              r_state<=sda_to_data_4;
        sda_to_data_3    : if(scl)
                            begin
                              data_from_sda[3]<=sda;
                              r_state<=sda_to_data_2;
                            end
                           else
                              r_state<=sda_to_data_3;
        sda_to_data_2    : if(scl)
                            begin
                              data_from_sda[2]<=sda;
                              r_state<=sda_to_data_1;
                            end
                           else
                              r_state<=sda_to_data_2;
       sda_to_data_1    : if(scl)
                            begin
                              data_from_sda[1]<=sda;
                              r_state<=sda_to_data_0;
                            end
                           else
                              r_state<=sda_to_data_1;
      sda_to_data_0    : if(scl)
                            begin
                              data_from_sda[0]<=sda;
                              r_state<=sda_to_data_end;
                            end
                           else
                              r_state<=sda_to_data_0;
    sda_to_data_end    : if(scl)
                            begin
                             link_read<=1'b1;
                            // link_sda<=1'b0;
                             ack_f<=1'b1;
                             r_state<=sda_to_data_7;
                            end
                          else
                            r_state<=sda_to_data_end;
                             
      /* default:  begin
                 link_read<=1'b0;
                 r_state<=sda_to_data_7;
               end*/
             endcase
           end
    endtask
    
    
    task perallel_to_serial;                   
      begin
        casex(w_state)
          data_to_sda_7: if(!scl)
                           begin
                             link_sda<=1'b1;
                             link_write<=1'b1;
                             w_state<=data_to_sda_6;
                           end
                         else
                             w_state<=data_to_sda_7;                  
            data_to_sda_6: if(!scl)
                           begin
                             link_sda<=1'b1;
                             link_write<=1'b1;
                             storage_buf<=storage_buf<<1;
                             w_state<=data_to_sda_5;
                           end
                         else
                             w_state<=data_to_sda_6; 
           data_to_sda_5: if(!scl)
                           begin
                             storage_buf<=storage_buf<<1;
                             w_state<=data_to_sda_4;
                           end
                         else
                             w_state<=data_to_sda_5;                
           data_to_sda_4: if(!scl)
                           begin
                             storage_buf<=storage_buf<<1;
                             w_state<=data_to_sda_3;
                           end
                         else
                             w_state<=data_to_sda_4; 
           data_to_sda_3: if(!scl)
                           begin
                             storage_buf<=storage_buf<<1;
                             w_state<=data_to_sda_2;
                           end
                         else
                             w_state<=data_to_sda_3; 
           data_to_sda_2: if(!scl)
                           begin
                             storage_buf<=storage_buf<<1;
                             w_state<=data_to_sda_1;
                           end
                         else
                             w_state<=data_to_sda_2; 
            data_to_sda_1: if(!scl)
                           begin
                             storage_buf<=storage_buf<<1;
                             w_state<=data_to_sda_0;
                           end
                         else
                             w_state<=data_to_sda_1; 
            data_to_sda_0: if(!scl)
                           begin
                             storage_buf<=storage_buf<<1;
                             w_state<=data_to_sda_end;
                           end
                         else
                             w_state<=data_to_sda_0; 
            data_to_sda_end: if(!scl)
                           begin
                             ack_f<=1'b1;
                             link_sda<=1'b0;
                             link_write<=1'b0;
                           end
                         //else
                           //  w_state<=data_to_sda_end;          
                     endcase
            end
      endtask
                         
        
       task head; 
      begin
        casex(head_state)
        head_begin:if(!scl)
                     begin
                       link_write<=1'b0;
                       link_sda<=1'b1;
                       link_head<=1'b1;
                       head_state<=head_bit;
                     end
                   else
                     head_state<=head_begin;
       head_bit:if(scl)
                     begin
                       ack_f<=1'b1;
                       head_buf<=head_buf<<1;
                       head_state<=head_end;
                     end
                   else
                     head_state<=head_bit;
       head_end:if(!scl)
                  begin
                  link_write<=1'b0;
                  link_head<=1'b0;
                  end 
                  else
                  head_state<=head_end;  
                endcase
              end
          endtask    
                  
                  
    task stop_1; 
      begin
        casex(stop_state)
        stop_begin: if(!scl)
                       begin
                       link_sda<=1'b1;
                       link_stop<=1'b1;
                       //link_write<=1'b0;
                       stop_state<=stop_bit;
                       end
                     else
                       stop_state<=stop_begin;
         stop_bit: if(scl)
                      begin
                       stop_buf<=stop_buf<<1;
                       stop_state<=stop_end;
                      end
                    else
                      stop_state<=stop_bit;
          stop_end: if(!scl)
                      begin
                        link_sda<=1'b0;
                        link_stop<=1'b0;
                        //link_write<=1'b0;
                        ack_f<=1'b1;
                      end
                    else
                      stop_state<=stop_end;
            endcase
        end
     endtask
                  
     
      always@(negedge clk)    //产生scl
        begin
          if(reset)
             scl<=0;
          else
             scl<=~scl;
         end
    
    always@(posedge clk)
    if(reset)
      begin
        link_read<=1'b0;
        link_write<=1'b0;
        link_head<=1'b0;
        link_stop<=1'b0;
        link_sda<=1'b0;
        ack=1'b0;
        ack_f=1'b0;
        head_buf<=2'b00;
        stop_buf<=2'b00;
        wf<=1'b0;
        rf<=1'b0;
        main_state<=idle;
      end
    else
      begin
      casex(main_state)
         idle: begin
              link_read<=1'b0;
              link_write<=1'b0;
              link_head<=1'b0;
              link_stop<=1'b0;
              link_sda<=1'b0;
              if(wr)
                begin
                 wf<=1'b1; 
                 main_state<=ready;
                end
              else if(rd)
                begin
                rf<=1'b1;
                main_state<=ready;
                end
              else
                begin
                rf<=1'b0;
                wf<=1'b0;
                main_state<=idle;
                end
              end
       ready: begin
               link_head<=1'b1;
               link_sda<=1'b1; 
               head_buf<=2'b10;
               stop_buf<=2'b01;
               head_state<=head_begin;
               main_state<=write_start;
               end
     write_start: if(ack_f==1'b0)
               head;
               else
                 begin
                   storage_buf<={1'b1,1'b0,1'b1,1'b0,addr[10:8],1'b0};
                   link_head<=1'b0;
                   link_write<=1'b1;
                   ack_f<=1'b0;
                   w_state<=data_to_sda_6;
                   main_state<=ctrl_write;
                  end  
    ctrl_write: if(ack_f==1'b0)
                 perallel_to_serial;
               else
                 begin
                   w_state<=data_to_sda_7;
                   ack_f<=1'b0;
                   storage_buf<=addr[7:0];
                   main_state<=addr_write;
                 end
     addr_write: if(ack_f==1'b0)
                   perallel_to_serial;
                 else if(wf==1'b1)
                   begin
                     ack_f<=1'b0;
                     main_state<=data_write;
                     storage_buf<=data;
                     w_state<=data_to_sda_7;
                   end
                 else if(rf==1'b1)
                   begin
                     ack_f<=1'b0;
                     main_state<=read_start;
                     head_state<=head_begin;
                     head_buf<=2'b10;
                   end
    data_write: if(ack_f==1'b0)
                 perallel_to_serial;
               else
                 begin
                 ack_f<=1'b0;
                 main_state<=stop;
                 stop_state<=stop_begin;
                // link_write<=1'b0;
               end
    read_start:if(ack_f==1'b0)
                 head;
               else
                 begin
                 ack_f<=1'b0;
                 main_state<=ctrl_read;
                 link_head<=1'b0;
                 storage_buf<={1'b1,1'b0,1'b1,1'b0,addr[10:8],1'b1};
                 link_write<=1'b1;
                 link_sda<=1'b1;
                 w_state<=data_to_sda_6;
                 end
    ctrl_read: if(ack_f==1'b0)
                 perallel_to_serial;
               else
                begin
                 ack_f<=1'b0;
                 link_write<=1'b0;
                 link_sda<=1'b0;
                 r_state<=sda_to_data_begin;
                 main_state<=data_read;
               end
     data_read: if(ack_f==1'b0)
                   serial_to_perallel;
                 else 
                   begin
                   ack_f<=1'b0;
                   main_state<=stop;
                   stop_state<=stop_bit;
                   link_stop<=1'b1;
                   link_sda<=1'b1;
                   end
       stop: if(ack_f==1'b0)
                stop_1;
              else 
              begin
              ack<=1'b1;
              ack_f<=1'b0;
              main_state<=ackn;
              end
       ackn: begin
              ack<=1'b0;
              wf<=1'b0;
              rf<=1'b0;
              main_state<=idle;
             end
     default:main_state<=idle;
     endcase
     end
                                                                      
         
    endmodule
    

    这里我想特别强调一下双向端口sda,究竟何时是输入端口何时是输出端口呢?在进行写入数据和控制字的写入时是输出端口,在进行应答位和数据读出时是接受外部数据的输入端口

    • link_sda是控制sda写入的开关
    • out_flag来自EEPROM的内部信号用于控制sda的输入。
      写过程双向sda信号的驱动.png读过程双向sda信号的驱动.png
      通过读写过程的波形图,我们可以看出,在读写过程中,link_sda和out_flag总是有一个信号保持高电平,这样的话才能保持sda信号有波形。
    signal

    为了测试EEPROM_WR,signal模块能够对被测试模块产生的ack信号产生相应,发出模仿MCU的数据、地址信号和读写信号;被测试的模块在接收到信号后会发出读写EEPROM虚拟模块的信号。
    本模块为行为模块,不能综合成门级网表。

        `timescale 1ns/1ns
        `define timeslice 200
         module signal(data,
              reset,
              clk,
              rd,
              wr,
              addr,
              ack
               );
         input ack;
         output clk,reset,rd,wr;
         output[10:0] addr;
         output[7:0] data;
    
         wire[7:0] data;
          reg clk,reset,rd,wr;
         reg[10:0] addr;
         reg W_R;
         reg[7:0] data_to_eeprom;
         reg[10:0] addr_mem[0:255];
         reg[7:0] data_mem[0:255];
         reg[7:0] ROM[0:2047];
         integer i,j;
         integer OUTFILE;
    
            parameter test_number=50;
    
             assign data=W_R ? 8'hzz : data_to_eeprom;
    
             always # (`timeslice/2)
            clk=~clk;
    
           initial
            begin
             reset=1;
             i=0;
             j=0;
             W_R=0;
             clk=0;
              rd=0;
             wr=0;
            # 1000
             reset=0;
           repeat(test_number)
           begin
            #(5 * `timeslice)
             wr=1;
             #(`timeslice)
             wr=0;
            @(posedge ack);
            end
            #(10 * `timeslice)
             W_R=1;
           repeat(test_number)
           begin
            #(5 * `timeslice)
             rd=1;
             #(`timeslice)
             rd=0;
               @(posedge ack);
             end
            end
    
          initial
            begin
              OUTFILE=$fopen("C:/Users/XQ/Desktop/eeprom_dat.txt");
              $readmemh("C:/Users/XQ/Desktop/addr_dat.txt",addr_mem);
              $readmemh("C:/Users/XQ/Desktop/data_dat.txt",data_mem);
            end
     
           initial 
            begin
               $display("writing-------------------------------writing");
               #(2*`timeslice)
               for(i=0;i<=test_number;i=i+1)
                begin
                  addr=addr_mem[i];
                   data_to_eeprom=data_mem[i];
                 $fdisplay(OUTFILE,"@%0h   %0h",addr,data_to_eeprom);
                @(posedge ack);
              end
              end  
    
                initial
               @(posedge W_R)
                 begin
                 addr=addr_mem[0];
                $fclose(OUTFILE);
                $readmemh("C:/Users/XQ/Desktop/eeprom_dat.txt",ROM);
                $display("begin----------------------reading");
               for(j=0;j<=test_number;j=j+1)
                 begin
                  addr=addr_mem[j];
                  @(posedge ack)
                    if(data==ROM[addr])
                     $display("data %0h == ROM[%0h]",data,addr);
                    else
                   $display("data %0h != ROM[%0h]",data,addr);
               end
            end
    
    EEPROM_ALL_TOP

    将各个部分集成在一起

    `timescale 1ns/1ns
    `define timeslice 200
    module EEPROM_ALL_top;
    
       wire[7:0] data;
       wire[10:0] addr;
       wire reset;
       wire clk,rd,wr,ack;
       wire sda,scl;
    
       parameter test_numbers=5;  
    
         initial
         begin        
        #(`timeslice*180*test_numbers)
        $stop;
       end
    
        signal #(test_numbers) signal_1(.data(data),
                                 .reset(reset),
                                   .clk(clk),
                                    .rd(rd),
                                    .wr(wr),
                                  .addr(addr),
                                  .ack(ack));
                                  
      EEPROM EEPROM_1 (.scl(scl),.sda(sda));
    
        EEPROM_WR EEPROM_WR_1 (.sda(sda),
                         .scl(scl),
                         .ack(ack),
                         .reset(reset),
                         .clk(clk),
                         .wr(wr),
                         .rd(rd),
                         .addr(addr),
                         .data(data));
    
    endmodule
    

    总结:其实要想彻底搞清楚这个复杂时序逻辑的工作机制,还是需要大家真正上手写一遍代码

  • 相关阅读:
    增强学习--值迭代
    makefile opencv的案例
    shiro拦截器处理链执行顺序
    HTTP头字段总结
    IntelliJ IDEA上创建Maven Spring MVC项目
    使用deploy命令发布jar到私服仓库nexus
    JAVA设计模式之单例模式
    java基础-I/O系统
    HTTP深入浅出 http请求
    HTTP Header 详解
  • 原文地址:https://www.cnblogs.com/xuqing125/p/8994067.html
Copyright © 2020-2023  润新知