// -----------------------------------------------------------------------------
// Copyright (c) 2010, ON Semiconductor.
// -----------------------------------------------------------------------------
// FILE NAME     : dig_i2c_fsm.v
// TYPE          : RTL
// AUTHOR        : Franck Da Costa
// AUTHORS EMAIL : Franck.DaCosta@onsemi.com
// -----------------------------------------------------------------------------
// RELEASE HISTORY
// VERSION DATE        AUTHOR           DESCRIPTION
//  1.0    2010-10-22  Franck Da Costa  Initial version
// -----------------------------------------------------------------------------
// KEYWORDS : 
// -----------------------------------------------------------------------------
// PURPOSE : 
// -----------------------------------------------------------------------------

module dig_i2c_fsm (
input             reset_b,
input             SCL,
input             SDA_in,
input             start,
input             stop,
input       [6:0] IC_ADR,
input       [7:0] read_bus,
input       [7:0] read_ibus,
input       [7:0] addr_ibus,
input             scan_mode,

output reg        WR,
output reg        RD,
output            i2c_load_adc,
output            SDA_out,
output reg        high_speed,
output reg  [7:0] write_bus,
output reg  [7:0] addr_bus
);

reg [1:0] SelectEmitReg;

wire RSTP;
wire SCL_b;

parameter [1:0] SHIFT    = 2'b00;
parameter [1:0] ACK      = 2'b01;
parameter [1:0] REG      = 2'b10;
parameter [1:0] REGNEXT  = 2'b11;

parameter  I2C_WRITE     = 1'b0;
parameter  I2C_READ      = 1'b1;

parameter  ACK_POSITION  = 4'b0111;
parameter  LAST_REC_BIT_POSITION  = 4'b1000;
parameter  ACK_CODE = 8'b01111111;

parameter Idle                = 7'b0000001;
parameter IC_address          = 7'b0000010;
parameter Register_address    = 7'b0000100;
parameter FIRST_WRITE         = 7'b0001000;
parameter READ                = 7'b0010000;
parameter OTHER_WRITE         = 7'b0100000;
parameter Incr_READ_address   = 7'b1000000;
parameter ACK_VAL             = 1'b0;

parameter MASTER_CODE         = 5'b00001;

reg [6:0] CurrentState;
reg [3:0] CptBit;
reg [7:0] ReceivedRegister;
reg [6:0] Synch_ReceivedRegister;

reg [7:0] EmitReg;
reg [7:0] EmitRegBis;

//------------------------------------------------------------------------------
// SDA Out NMOS driver
//------------------------------------------------------------------------------
assign SDA_out = ~EmitReg[7];

assign SCL_b = (scan_mode) ? SCL : ~SCL;

//------------------------------------------------------------------------------
// Emit registers
//------------------------------------------------------------------------------
always @ (posedge SCL_b or negedge reset_b) begin
   if(!reset_b) begin
      EmitReg    <= 8'b11111111;
      EmitRegBis <= 8'b11111111;
   end
   else begin
      case(SelectEmitReg)
      ACK : begin 
         EmitReg    <= ACK_CODE;
      end
      
      REG : begin
         EmitReg    <= read_bus;
         EmitRegBis <= read_ibus;
      end
      
      REGNEXT : begin
         if (addr_bus == addr_ibus) begin
            EmitReg    <= EmitRegBis;
            EmitRegBis <= read_ibus;
         end
         else begin
            EmitReg    <= read_bus;
            EmitRegBis <= read_ibus;
         end
      end
      
      default : begin
         EmitReg    <= {EmitReg[6:0],1'b1};
      end
      
      endcase
   end
end

assign i2c_load_adc = CptBit[2];

//------------------------------------------------------------------------------
// Synchro registers
//------------------------------------------------------------------------------
always @ (posedge SCL_b or negedge reset_b) begin
   if(!reset_b) begin
      Synch_ReceivedRegister <= 7'b0000000;
   end
   else begin
      Synch_ReceivedRegister <= ReceivedRegister[6:0];
   end
end

//------------------------------------------------------------------------------
// High Speed 
//------------------------------------------------------------------------------
assign RSTP = (scan_mode) ? reset_b : reset_b & !stop;

always @ (posedge SCL or negedge RSTP ) begin
   if (!RSTP) // (stop || !reset_b)
      high_speed <= 1'b0;
   else if ((CurrentState == IC_address) && (CptBit == LAST_REC_BIT_POSITION) && (!start))
      if ((ReceivedRegister[7:3] == MASTER_CODE) && (SDA_in == 1'b1))
         high_speed <= 1'b1;
end


//------------------------------------------------------------------------------
// FSM 
//------------------------------------------------------------------------------
always @ (posedge SCL or negedge reset_b) begin
   if(!reset_b) begin
      CurrentState     <= Idle;
      ReceivedRegister <= 8'b00000000;
      CptBit           <= 4'b0001;
      SelectEmitReg    <= SHIFT;
      WR               <= 1'b0;
      RD               <= 1'b0;
      addr_bus         <= 8'b00000000;
      write_bus        <= 8'b00000000;
   end
   else begin
   
      if (CptBit == 4'b1000) begin
         CptBit <= 4'b0000;
      end
      else begin
         CptBit <= CptBit + 4'b0001;
      end

      // Default value
      SelectEmitReg    <= SHIFT;
      WR               <= 1'b0;
      RD               <= 1'b0;
      ReceivedRegister <= {Synch_ReceivedRegister[6:0],SDA_in};
      
      // RAZ sur un depart de reception
      if (start) begin
         CurrentState <= IC_address;
         CptBit       <= 4'b0001;
      end
      
      // FSM
      else
      case (CurrentState)
      
      Idle: begin
         WR  <= 1'b0;
         RD  <= 1'b0;
      end

      IC_address: begin
         // Test de l'adresse et continuation automate ou
         // retour en IDLE. Si commande de read alors RD est
         // possitionne
         if (CptBit == ACK_POSITION) begin
            if (ReceivedRegister[6:0] == IC_ADR) begin
               SelectEmitReg <= ACK;
//                if (SDA_in == I2C_READ)
//                   RD <= 1'b1;
            end
            else if (ReceivedRegister[6:2] != MASTER_CODE) begin
               CurrentState  <= Idle;
            end
         end
         
         //Test read ou write et fait evoluer la machine
         //en consequence
         else if (CptBit == LAST_REC_BIT_POSITION) begin
            // Write action
            if (ReceivedRegister[7:3] == MASTER_CODE)  begin
               CurrentState  <= Idle;
            end
            else if (ReceivedRegister[0] == I2C_WRITE) begin
               CurrentState <= Register_address;
            end
            else begin
               SelectEmitReg <= REG;
               CurrentState  <= READ;
            end
         end
      end

      Register_address: begin
         //Recuperation adresse d'ecriture + generation
         //Acknowledge.
         
         if (CptBit == ACK_POSITION) begin
            SelectEmitReg <= ACK;
         end
         else if (CptBit == LAST_REC_BIT_POSITION) begin
            addr_bus      <= ReceivedRegister;
            CurrentState <= FIRST_WRITE;
         end
      end

      FIRST_WRITE: begin
         //Premier Write. Pas d'increment d'adresse
         if (CptBit == ACK_POSITION) begin
            // Prepare emission de ACK sur fornt descendant
            // suivant et sort la donnee a ecrire
            SelectEmitReg <= ACK;
            write_bus    <= {ReceivedRegister[6:0], SDA_in};
         end
         else if (CptBit == LAST_REC_BIT_POSITION) begin
            //Positionne le signal Write et passe
            //a l'etat autres ecritures
            WR  <= 1'b1;
            CurrentState <= OTHER_WRITE;
         end
      end

      OTHER_WRITE: begin
         //Autres ecritures. Idem FirstWrite mais avec incement
         //d'adresse
         if (CptBit == ACK_POSITION) begin
            SelectEmitReg <= ACK;
            write_bus    <= {ReceivedRegister[6:0], SDA_in};

            if (addr_bus<8'b11111111) begin
               addr_bus <= addr_bus + 8'd1;
            end

         end
         else if (CptBit == LAST_REC_BIT_POSITION) begin
            WR  <= 1'b1;
         end
      end

      READ: begin
         // Gestion des Read. Genere la sequence
         // Increment adresse sur le bus, positionnement
         // du RD en anticipe puis commande sortie
         // nouvelle donnee si Acnowledge positif
         // ou arret envoie si Acknowledge negatif
         if (CptBit == ACK_POSITION - 4'b0001) begin

            if (addr_bus<8'b11111111) begin
               addr_bus <= addr_bus + 8'd1;
            end

         end
         else if (CptBit == 4'b0000) begin
            RD <= 1'b1;
         end

         else if (CptBit == LAST_REC_BIT_POSITION) begin
            //Test si fin de reception
            if (SDA_in == ACK_VAL) begin
               //non recharge la nouvelle valeur
               SelectEmitReg <= REGNEXT;
            end
            else begin
               //oui retourne en Idle. reinitialise le registre
               //d'adresse
               CurrentState  <= Idle;
               addr_bus       <= addr_bus - 8'd1;
            end
         end
      end

      default : begin
         CurrentState <= Idle;
      end
      
      endcase
   end
end

endmodule

