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

module dig_cp_clk (
//Inputs
input              por_b,            // Asynchronous reset active low
input              clk_int,          // 12MHz oscillator clock
input              clk_ext,          // CLI (400kHz-27MHz)
input              div2_en,          // CP to be in div2 mode
input              clo_dis,          // CLO output disable 
input              slave,            // Slave mode
input              otp_clo_inv,      // Scan mode
input              scan_mode,        // Scan mode
//Outputs
output             clk_cp_div2,      // CP clock ~850kHz
output             clk_cp_pt,        // CP clock ~850kHz (internal en slave)
output             clk_out,          // CLO (inverted clk_cp)
output reg         clk_ext_range_ok, // CLI is in range
output reg         clk_ext_used,     // CLI used for CP clock
output       [3:0] n_cli             // N_factor used for CLI division
);

/// ****************************************************************************
/// Declarations
/// ****************************************************************************

reg          [4:0] ext_div_np1;
reg          [2:0] ext_div_8xnp1;
reg          [3:0] ext_div_n;
reg          [7:0] cnt_int;
wire         [4:0] next_n_factor;
wire         [4:0] n_factor_p1;
reg                clk_ext_div_8xnp1_reg1;
reg                clk_ext_div_8xnp1_reg2;
reg                clk_ext_div_8xnp1_reg3;
reg                clk_ext_div_ngt1;
reg          [2:0] int_div_14;
reg                clk_int_div_14;
reg                clk_int_2_ext;
reg                clk_ext_div_n_reg1;
reg                clk_ext_div_n_reg2;
reg                clk_ext_div_n_reg3;
reg          [5:0] cnt_range;
wire               clk_ext_div_8xnp1_posedge;
wire               clk_ext_div_n;
wire               clk_ext_div_n_posedge;

reg                n_factor_change;
reg                n_factor_change_syncb;
reg                n_factor_change_sync;

wire         [4:0] n_factor_p1_ext;
reg          [4:0] n_factor_ext;
reg          [4:0] n_factor;         


// -----------------------------------------------------------------------------
// FSM state
// -----------------------------------------------------------------------------
parameter IDLE        = 4'b0001;
parameter CHECK_RANGE = 4'b0010;
parameter DONE        = 4'b0100;
parameter DET         = 4'b1000;

`define s_IDLE         state[0]
`define s_CHECK_RANGE  state[1]
`define s_DONE         state[2]
`define s_DET          state[3]

`define ns_IDLE        next_state[0]
`define ns_CHECK_RANGE next_state[1]
`define ns_DONE        next_state[2]
`define ns_DET         next_state[3]

reg      [3:0] state;
reg      [3:0] next_state;

/// ****************************************************************************
/// Processes
/// ****************************************************************************

// CLI divided by N+1 (for algorithm)
// -----------------------------------------------------------------------------
always @ (posedge clk_ext or negedge por_b) begin
   if (!por_b) begin
      ext_div_np1   <= 5'd0;
      ext_div_8xnp1 <= 3'd7;
   end
   else if (`s_DONE) begin
      ext_div_np1   <= 5'd0;
      ext_div_8xnp1 <= 3'd7;
   end
   else if (ext_div_np1 == n_factor_p1_ext) begin
      ext_div_np1   <= 5'd0;
      ext_div_8xnp1 <= ext_div_8xnp1 - 3'd1;
   end
   else begin
      ext_div_np1   <= ext_div_np1 + 5'd1;
   end
end

// N synced on CLI domain
// -----------------------------------------------------------------------------

assign n_factor_change_rstb = ( scan_mode )? por_b : por_b & ~n_factor_change_sync;

always @ (posedge clk_int or negedge n_factor_change_rstb) begin
   if (!n_factor_change_rstb) begin
      n_factor_change <= 1'b0;
   end
   else if (`ns_IDLE && !`s_IDLE) begin
      n_factor_change <= 1'b1;
   end
   else if (`s_DET) begin
      if (clk_ext_div_8xnp1_posedge) begin
         if (cnt_int < 8'd112) begin
            n_factor_change <= 1'b1;
         end
      end
   end
end

assign clk_ext_scan = ( scan_mode )?  ~clk_ext : clk_ext;

always @ (negedge clk_ext_scan or negedge por_b) begin
   if (!por_b) begin
      n_factor_change_syncb <= 1'b0;
   end
   else begin
      n_factor_change_syncb <= n_factor_change;
   end
end

always @ (posedge clk_ext or negedge por_b) begin
   if (!por_b) begin
      n_factor_change_sync <= 1'b0;
   end
   else begin
      n_factor_change_sync <= n_factor_change_syncb;
   end
end

always @ (posedge clk_ext or negedge por_b) begin
   if (!por_b) begin
      n_factor_ext <= 5'd0;
   end
   else if (n_factor_change_sync) begin
      n_factor_ext <= n_factor;
   end
end

// N+1 on CLI domain
// -----------------------------------------------------------------------------
assign n_factor_p1_ext[4:1] = n_factor_ext[4:1] + {3'b000,n_factor_ext[0]};
assign n_factor_p1_ext[0]   = ~n_factor_ext[0];



// CLI divided by 8x(N+1)
// -----------------------------------------------------------------------------
always @ (posedge clk_int or negedge por_b) begin
   if (!por_b) begin
      clk_ext_div_8xnp1_reg1 <= 1'b1;
      clk_ext_div_8xnp1_reg2 <= 1'b1;
      clk_ext_div_8xnp1_reg3 <= 1'b1;
   end
   else if (!`s_DONE) begin
      clk_ext_div_8xnp1_reg1 <= ext_div_8xnp1[2];
      clk_ext_div_8xnp1_reg2 <= clk_ext_div_8xnp1_reg1;
      clk_ext_div_8xnp1_reg3 <= clk_ext_div_8xnp1_reg2;
   end
end

// CLI divided by 8x(N+1) - posedge
// -----------------------------------------------------------------------------
assign clk_ext_div_8xnp1_posedge = clk_ext_div_8xnp1_reg2 & ~clk_ext_div_8xnp1_reg3;

always @ (posedge clk_int or negedge por_b) begin
   if (!por_b) begin
      cnt_int <= 8'd0;
   end
   else if (clk_ext_div_8xnp1_posedge) begin
      cnt_int <= 8'd0;
   end
   else if ((cnt_int != 8'hff) && (`s_DET || `s_CHECK_RANGE)) begin
      cnt_int <= cnt_int + 8'd1;
   end
end

// Next N during detection
// -----------------------------------------------------------------------------
assign next_n_factor[4:1] = (n_factor == 5'd27) ? n_factor [4:1] : n_factor[4:1] + {3'b000,n_factor[0]};
assign next_n_factor[0]   = 1'b1;

// N factor update
// -----------------------------------------------------------------------------
always @ (posedge clk_int or negedge por_b) begin
   if (!por_b) begin
      n_factor <= 5'd0;
   end
   else if (`ns_IDLE) begin
      n_factor <= 5'd0;
   end
   else if (`s_DET) begin
      if (clk_ext_div_8xnp1_posedge) begin
         if (cnt_int < 8'd112) begin
            n_factor <= next_n_factor;
         end
      end
   end
end

// FSM
// -----------------------------------------------------------------------------
always @ (posedge clk_int or negedge por_b) begin
   if (!por_b) begin
      state <= IDLE;
   end
   else begin
      state <= next_state;
   end
end

always @ (*) begin
   next_state = state;
   case (state)
   IDLE : begin
      if (clk_ext_div_8xnp1_posedge && !slave) begin
         next_state = CHECK_RANGE;
      end
   end
   
   CHECK_RANGE : begin
      if (clk_ext_div_8xnp1_posedge) begin
         if ((cnt_int >= 8'd112) && clk_ext_range_ok) begin
            next_state = DONE;
         end
         else begin
            next_state = DET;
         end
      end
   end
   
   DONE : begin
      if (!clk_ext_range_ok) begin
         next_state = IDLE;
      end
   end
   
   DET : begin
      if (clk_ext_div_8xnp1_posedge && (cnt_int >= 8'd112) && (cnt_int < 8'd224) ) begin
         next_state = DONE;
      end
      else if ((n_factor == 5'd27) && clk_ext_range_ok)begin
         next_state = DONE;
      end
   end

   default : next_state = IDLE;
   
   endcase
end


// Internal clock divided by 14
// -----------------------------------------------------------------------------
always @ (posedge clk_int or negedge por_b) begin
   if (!por_b) begin
      int_div_14     <= 3'd0;
      clk_int_div_14 <= 1'b0;
   end
   else if (int_div_14 == 3'd0) begin
      int_div_14     <= 3'd6;
      clk_int_div_14 <= ~clk_int_div_14;
   end
   else begin
      int_div_14     <= int_div_14 - 3'd1;
   end
end

// External clock divided by N=n_factor_ext+1 (DC=50%)
// -----------------------------------------------------------------------------
always @ (posedge clk_ext or negedge por_b) begin
   if (!por_b) begin
      ext_div_n        <= 4'd0;
      clk_ext_div_ngt1 <= 1'b0;
   end
   else if (ext_div_n == 4'd0) begin
      ext_div_n        <= n_factor_ext[4:1];
      clk_ext_div_ngt1 <= ~clk_ext_div_ngt1;
   end
   else begin
      ext_div_n        <= ext_div_n - 4'd1;
   end
end

assign clk_ext_div_n = (n_factor_ext == 5'd0) ? clk_ext : clk_ext_div_ngt1;

// Range check
// -----------------------------------------------------------------------------
always @ (posedge clk_int or negedge por_b) begin
   if (!por_b) begin
      clk_ext_div_n_reg1 <= 1'b0;
      clk_ext_div_n_reg2 <= 1'b0;
      clk_ext_div_n_reg3 <= 1'b0;
   end
   else begin
      clk_ext_div_n_reg1 <= clk_ext_div_n;
      clk_ext_div_n_reg2 <= clk_ext_div_n_reg1;
      clk_ext_div_n_reg3 <= clk_ext_div_n_reg2;
   end
end

assign clk_ext_div_n_posedge = clk_ext_div_n_reg2 & ~clk_ext_div_n_reg3;

always @ (posedge clk_int or negedge por_b) begin
   if (!por_b) begin
      cnt_range <= 6'h3f;
   end
   else if (clk_ext_div_n_posedge) begin
      cnt_range <= 6'd0;
   end
   else if (cnt_range != 6'h3f) begin
      cnt_range <= cnt_range + 6'd1;
   end
end

always @ (posedge clk_int or negedge por_b) begin
   if (!por_b) begin
      clk_ext_range_ok <= 1'b0;
   end
   else if (slave) begin
      if (clk_ext_div_n_posedge && !clk_ext_range_ok) begin
         clk_ext_range_ok <= 1'b1;
      end
      else if (cnt_range > 6'd55) begin
         clk_ext_range_ok <= 1'b0;
      end
   end
   else if (cnt_range > (clk_ext_range_ok ? 6'd28 : 6'd27)) begin
      clk_ext_range_ok <= 1'b0;
   end
   else if (clk_ext_div_n_posedge) begin
      if (cnt_range > (clk_ext_range_ok ? 6'd5 : 6'd6)) begin
         clk_ext_range_ok <= 1'b1;
      end
      else begin
         clk_ext_range_ok <= 1'b0;
      end
   end
end

// Internal/External clock switch
// -----------------------------------------------------------------------------
always @ (posedge clk_int or negedge por_b) begin
   if (!por_b) begin
      clk_int_2_ext <= 1'b0;
   end
   else if (!clk_ext_range_ok) begin
      clk_int_2_ext <= 1'b0;
   end
   else if (`s_DONE) begin
      if(!clk_int_div_14) begin
         clk_int_2_ext <= 1'b1;
      end
   end
   else begin
      clk_int_2_ext <= 1'b0;
   end
end
always @ (posedge clk_int or negedge por_b) begin
   if (!por_b) begin
      clk_ext_used   <= 1'b0;
   end
   else if (!clk_ext_range_ok) begin
      if(clk_int_div_14 == clk_ext_div_n_reg2) begin
         clk_ext_used <= 1'b0;
      end
   end
   else if (`s_DONE) begin
      if(clk_int_2_ext && !clk_ext_div_n_reg2) begin
         clk_ext_used   <= 1'b1;
      end
   end
   else begin
      clk_ext_used   <= 1'b0;
   end
end

/// -----------------------------------------------------------------------------
/// CP clocks
/// -----------------------------------------------------------------------------

// CP clock for div2 opearation
// -----------------------------------------------------------------------------
//    - force on CLI in slave
//    - CLI/N or clk_int/14 in master

assign clk_cp_div2_tmp = (slave) ? clk_ext :
                         (clk_ext_used) ? ((clk_ext_range_ok) ? clk_ext_div_n : clk_ext_div_n_reg2 | clk_ext_div_n_reg3) :
                                           clk_int_div_14 & ~clk_int_2_ext;
                                    
assign clk_cp_div2 = div2_en & clk_cp_div2_tmp;
     
// CP clock for pass through opearation
// -----------------------------------------------------------------------------
assign clk_cp_pt = clk_int_div_14;

/// -----------------------------------------------------------------------------
/// CLO: only in slave and gatable in master
/// -----------------------------------------------------------------------------
assign clk_out = ~clo_dis & div2_en & ~slave & ((otp_clo_inv) ? clk_cp_div2 : ~clk_cp_div2);

/// -----------------------------------------------------------------------------
/// N_CLI
/// -----------------------------------------------------------------------------
assign n_cli = (!clk_ext_used) ? 4'd0 : n_factor_p1_ext[4:1] + 4'd1;


`undef s_IDLE        
`undef s_CHECK_RANGE 
`undef s_DONE        
`undef s_DET         

`undef ns_IDLE       
`undef ns_CHECK_RANGE
`undef ns_DONE       
`undef ns_DET        

endmodule
