// SPDX-FileCopyrightText: 2020 Efabless Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// SPDX-License-Identifier: Apache-2.0

/* Integer-N clock divider */
`default_nettype none
 
module clock_div #(
    parameter SIZE = 3		// Number of bits for the divider value
) (
    in, out, N, resetb
);
    input in;			// input clock
    input [SIZE-1:0] N;		// the number to be divided by
    input resetb;		// asynchronous reset (sense negative)
    output out;			// divided output clock
 
    wire out_odd;		// output of odd divider
    wire out_even;		// output of even divider
    wire not_zero;		// signal to find divide by 0 case
    wire enable_even;		// enable of even divider
    wire enable_odd;		// enable of odd divider

    reg [SIZE-1:0] syncN;	// N synchronized to output clock
    reg [SIZE-1:0] syncNp;	// N synchronized to output clock
 
    assign not_zero = | syncN[SIZE-1:1];
 
    assign out = (out_odd & syncN[0] & not_zero) | (out_even & !syncN[0]);
    assign enable_odd = syncN[0] & not_zero;
    assign enable_even = !syncN[0];

    // Divider value synchronization (double-synchronized to avoid metastability)
    always @(posedge out or negedge resetb) begin
	if (resetb == 1'b0) begin
	    syncN <= `CLK_DIV;	// Default to divide-by-2 on system reset
	    syncNp <= `CLK_DIV;	// Default to divide-by-2 on system reset
	end else begin
	    syncNp <= N;
	    syncN <= syncNp;
	end
    end
 
    // Even divider
    even even_0(in, out_even, syncN, resetb, not_zero, enable_even);
    // Odd divider
    odd odd_0(in, out_odd, syncN, resetb, enable_odd);
 
endmodule // clock_div
 
/* Odd divider */

module odd #(
    parameter SIZE = 3
) (
    clk, out, N, resetb, enable
);
    input clk;			// slow clock
    output out;			// fast output clock
    input [SIZE-1:0] N;		// division factor
    input resetb;		// synchronous reset
    input enable;		// odd enable
 
    reg [SIZE-1:0] counter;	// these 2 counters are used
    reg [SIZE-1:0] counter2;	// to non-overlapping signals
    reg out_counter;		// positive edge triggered counter
    reg out_counter2;		// negative edge triggered counter
    reg rst_pulse;		// pulse generated when vector N changes
    reg [SIZE-1:0] old_N;	// gets set to old N when N is changed
    wire not_zero;		// if !not_zero, we devide by 1
 
    // xor to generate 50% duty, half-period waves of final output
    assign out = out_counter2 ^ out_counter;

    // positive edge counter/divider
    always @(posedge clk or negedge resetb) begin
	if (resetb == 1'b0) begin
	    counter <= `CLK_DIV;
	    out_counter <= 1;
	end else if (rst_pulse) begin
	    counter <= N;
	    out_counter <= 1;
	end else if (enable) begin
	    if (counter == 1) begin
		counter <= N;
		out_counter <= ~out_counter;
	    end else begin
		counter <= counter - 1'b1;
	    end
	end
    end
 
    reg [SIZE-1:0] initial_begin;	// this is used to offset the negative edge counter
    wire [SIZE:0] interm_3;		// from the positive edge counter in order to
    assign interm_3 = {1'b0, N} + 2'b11;	// guarantee 50% duty cycle.

    localparam [SIZE:0] interm_init = {1'b0,`CLK_DIV} + 2'b11;

    // Counter driven by negative edge of clock.

    always @(negedge clk or negedge resetb) begin
	if (resetb == 1'b0) begin
	    // reset the counter at system reset
	    counter2 <= `CLK_DIV;
	    initial_begin <= interm_init[SIZE:1];
	    out_counter2 <= 1;
	end else if (rst_pulse) begin
	    // reset the counter at change of N.
	    counter2 <= N;
	    initial_begin <= interm_3[SIZE:1];
	    out_counter2 <= 1;
	end else if ((initial_begin <= 1) && enable) begin

	    // Do normal logic after odd calibration.
	    // This is the same as the even counter.
	    if (counter2 == 1) begin
		counter2 <= N;
		out_counter2 <= ~out_counter2;
	    end else begin
		counter2 <= counter2 - 1'b1;
	    end
	end else if (enable) begin
	    initial_begin <= initial_begin - 1'b1;
	end
    end
 
    //
    // reset pulse generator:
    //               __    __    __    __    _
    // clk:       __/  \__/  \__/  \__/  \__/
    //            _ __________________________
    // N:         _X__________________________
    //               _____
    // rst_pulse: __/     \___________________
    //
    // This block generates an internal reset for the odd divider in the
    // form of a single pulse signal when the odd divider is enabled.

    always @(posedge clk or negedge resetb) begin
	if (resetb == 1'b0) begin
	    rst_pulse <= 0;
	end else if (enable) begin
	    if (N != old_N) begin
		// pulse when reset changes
		rst_pulse <= 1;
	    end else begin
		rst_pulse <= 0;
	    end
	end
    end
 
    always @(posedge clk) begin
	// always save the old N value to guarante reset from
	// an even-to-odd transition.
	old_N <= N;
    end	
 
endmodule // odd

/* Even divider */

module even #(
    parameter SIZE = 3
) (
    clk, out, N, resetb, not_zero, enable
);
    input clk;		// fast input clock
    output out;		// slower divided clock
    input [SIZE-1:0] N;	// divide by factor 'N'
    input resetb;	// asynchronous reset
    input not_zero;	// if !not_zero divide by 1
    input enable;	// enable the even divider
 
    reg [SIZE-1:0] counter;
    reg out_counter;
    wire [SIZE-1:0] div_2;
 
    // if N=0 just output the clock, otherwise, divide it.
    assign out = (clk & !not_zero) | (out_counter & not_zero);
    assign div_2 = {1'b0, N[SIZE-1:1]};
 
    // simple flip-flop even divider
    always @(posedge clk or negedge resetb) begin
	if (resetb == 1'b0) begin
	    counter <= 1;
	    out_counter <= 1;

	end else if (enable) begin
	    // only use switching power if enabled
	    if (counter == 1) begin
		// divide after counter has reached bottom
		// of interval 'N' which will be value '1'
		counter <= div_2;
		out_counter <= ~out_counter;
	    end else begin
		// decrement the counter and wait
		counter <= counter-1;	// to start next transition.
	    end
	end
    end
 
endmodule //even
`default_nettype wire