/*
 * 3signal.v
 *
 * vim: ts=4 sw=4
 *
 * Copyright (C) 2025 Krzysztof Skrzynecki, Jakub Duchniewicz <j.duchniewicz@gmail.com>
 * SPDX-License-Identifier: TODO:
 */

`default_nettype none

module continous_pwm_gen#(
    parameter WIDTH = 16
) (
    input wire nrst, clk,
    input [WIDTH-1:0] period,
    input [WIDTH-1:0] delay,

    output wire [0:0] last_tick,
    output wire [0:0] mid_tick,

    output reg [0:0] pwm_out
);
    reg [WIDTH-1:0] counter;
    wire [WIDTH-1:0] next_cnt=counter+1;
    wire [WIDTH-1:0] last_avail_cnt=period-1;

    // Counter logic
    always_ff @(posedge clk or negedge nrst) begin
        if (!nrst) begin
            counter <= 0;
            pwm_out <= 0;
        end else begin
            if(counter >= last_avail_cnt) begin
                counter <= 0;
            end else begin
                counter <= next_cnt;
            end

            if ((next_cnt<delay) || (counter>=last_avail_cnt)) begin
                pwm_out <= 0;
            end else begin
                pwm_out <= 1;
            end
        end
    end

    assign last_tick = (next_cnt>=period);
    assign mid_tick = ((next_cnt)==(period>>1));

endmodule

module single_shot_gen#(
    parameter WIDTH = 16
)(
    input wire nrst, clk,
    input wire trigger,
    input [WIDTH-1:0] delay,
    input [WIDTH-1:0] duty,

    output reg [0:0] pwm_out
);
    reg [WIDTH-1:0] counter;
    reg [0:0] is_counting;
    wire [WIDTH-1:0] next_cnt=counter+1;
    wire [WIDTH-1:0] period=delay+duty;
    //wire [WIDTH-1:0] last_avail_cnt=period-1;

    always_ff @(posedge clk or negedge nrst) begin
        if (!nrst) begin
            counter <= 0;
            is_counting <= 0;
        end else begin
            if (is_counting) begin

                if(counter+1 >= period) begin
                    counter <= 0;
                    is_counting <= 0;
                    pwm_out <= 0;
                end else begin
                    counter <= next_cnt;

                    if (next_cnt>=delay) begin
                        pwm_out <= 1;
                    end else begin
                        pwm_out <= 0;
                    end
                end
            end else begin
                if (trigger) begin
                    is_counting <= 1;

                    if ((delay==0) && (duty!=0)) begin
                        pwm_out <= 1;
                    end
                end
            end
        end
    end
endmodule

module phase_delay#(
    parameter WIDTH = 16,
    parameter FORWARD_DATA_WIDTH = 16
)(
    input wire nrst, clk,
    input wire in_trig,
    input [WIDTH-1:0] delay,
    input [FORWARD_DATA_WIDTH-1:0] in_data_fwd,

    output wire out_trig,
    output reg [FORWARD_DATA_WIDTH-1:0] out_data_fwd
);
    reg [WIDTH-1:0] counter;
    reg [0:0] is_counting;

    reg [WIDTH-1:0] delay_latched;

    always_ff @(posedge clk or negedge nrst) begin
        if (!nrst) begin
            counter <= 0;
            is_counting <=0;
            delay_latched <=0;
        end else begin
            if (is_counting) begin
                if ((counter+1) >= delay_latched) begin
                    counter <= 0;
                    is_counting <= 0;
                end else begin
                    counter <= counter+1;
                end
            end else begin
                if (in_trig) begin
                    is_counting <= 1;
                    out_data_fwd <= in_data_fwd;
                    delay_latched <= delay;
                end
            end

        end
    end

    assign out_trig=((counter+1)>=delay) && is_counting;
endmodule

module pulse_train_gen#(
    parameter WIDTH = 16,
    parameter PULSE_COUNTER_WIDTH = 16
)(
    input wire nrst, clk,
    input wire trigger,
    input [PULSE_COUNTER_WIDTH-1:0] npuls,
    input [WIDTH-1:0] period,
    input [WIDTH-1:0] duty,

    output reg [0:0] pwm_out
);
    reg [WIDTH-1:0] counter;
    reg [0:0] is_counting;
    reg [PULSE_COUNTER_WIDTH-1:0] remaining_pulses;

    reg [WIDTH-1:0] period_local;
    reg [WIDTH-1:0] duty_local;

    wire [WIDTH-1:0] next_cnt = counter+1;

    always_ff @(posedge clk or negedge nrst) begin
        if (!nrst) begin
            counter <= 0;
            is_counting <= 0;
            pwm_out <=0;
        end else begin
            if (is_counting) begin

                if(next_cnt >= period) begin
                    counter <= 0;

                    if (remaining_pulses > 0) begin
                        remaining_pulses <= remaining_pulses-1;
                        pwm_out <= 1;

                    end else begin
                        pwm_out <= 0;
                        is_counting <= 0;
                    end

                end else begin
                    counter <= next_cnt;

                    if (next_cnt>=duty) begin
                        pwm_out <= 0;
                    end else begin
                        pwm_out <= 1;
                    end
                end
            end else begin
                if (trigger) begin
                    if ((period>0) && (duty!=0) && (npuls>0)) begin
                        pwm_out <= 1;
                        remaining_pulses <= npuls-1;
                        is_counting <= 1;
                        counter <= 0;
                        period_local <= period;
                        duty_local <= duty;
                    end
                end
            end
        end
    end
endmodule

typedef enum logic [1:0] {
    ODD_TRAIN_FORCE_OFF,
    ODD_TRAIN_ENA_CONTROL,
    ODD_TRAIN_FORCE_ON
} odd_train_flag_t;

module three_signal#(
    parameter FAST_PWM_WIDTH=8,
    parameter PULSE_COUNTER_WIDTH=8,
    parameter SLOW_PWM_WIDTH=14
)(
    input wire nrst, clk,

    input [SLOW_PWM_WIDTH-1:0] period1,
    input [SLOW_PWM_WIDTH-1:0] delay1,

    input [SLOW_PWM_WIDTH-1:0] duty2,
    input [SLOW_PWM_WIDTH-1:0] delay2,

    input [FAST_PWM_WIDTH-1:0] period3,
    input [FAST_PWM_WIDTH-1:0] duty3,
    input [SLOW_PWM_WIDTH-1:0] delay3,//delay is wrt slow pwm, thus longer bit length
    input [PULSE_COUNTER_WIDTH-1:0] npuls3,

    input /*odd_train_flag_t*/ wire [1:0] odd_train_flag,

    input wire ena_odd_out3,

    output reg Out1,
    output reg Out2,
    output reg Out3
);
    wire trigger_next_cycle;
    wire trigger_even_cycle;
    wire trigger_odd_cycle;

    wire [SLOW_PWM_WIDTH-1:0] delay3_part1;
    wire [SLOW_PWM_WIDTH-1:0] delay3_part234;

    assign delay3_part234 = delay3>>2;
    assign delay3_part1 = (delay3<4) ? 0 : (delay3 - (delay3_part234*3));

    continous_pwm_gen #(.WIDTH(SLOW_PWM_WIDTH)) pwm1(
        .nrst(nrst),
        .clk(clk),
        .period(period1),
        .delay(delay1),
        .last_tick(trigger_next_cycle),
        .mid_tick(trigger_even_cycle),
        .pwm_out(Out1)
    );

    single_shot_gen #(.WIDTH(SLOW_PWM_WIDTH)) pwm2(
        .nrst(nrst),
        .clk(clk),
        .trigger(trigger_next_cycle),
        .delay(delay2),
        .duty(duty2),
        .pwm_out(Out2)
    );

    wire trigger_delay_1_to_2;
    wire trigger_delay_2_to_3;
    wire trigger_delay_3_to_4;
    wire trigger_delay_4_to_pulse_train;

    localparam FORWARD_DATA_WIDTH = SLOW_PWM_WIDTH + PULSE_COUNTER_WIDTH + FAST_PWM_WIDTH + FAST_PWM_WIDTH;
    localparam DELAY_BITS_POS = PULSE_COUNTER_WIDTH + FAST_PWM_WIDTH + FAST_PWM_WIDTH;
    wire [FORWARD_DATA_WIDTH-1:0] data_1_to_2;
    wire [FORWARD_DATA_WIDTH-1:0] data_2_to_3;
    wire [FORWARD_DATA_WIDTH-1:0] data_3_to_4;
    wire [FORWARD_DATA_WIDTH-1:0] data_4_to_gen;

    assign trigger_odd_cycle =
        (odd_train_flag == ODD_TRAIN_ENA_CONTROL) ? trigger_next_cycle && ena_odd_out3 :
        (odd_train_flag == ODD_TRAIN_FORCE_ON) ? trigger_next_cycle :
        0;

    wire [FAST_PWM_WIDTH-1:0] period3_gen;
    wire [FAST_PWM_WIDTH-1:0] duty3_gen;
    wire [PULSE_COUNTER_WIDTH-1:0] npuls3_gen;

    assign duty3_gen = data_4_to_gen[FAST_PWM_WIDTH-1:0];
    assign period3_gen = data_4_to_gen[FAST_PWM_WIDTH+FAST_PWM_WIDTH-1:FAST_PWM_WIDTH];
    assign npuls3_gen = data_4_to_gen[FAST_PWM_WIDTH+FAST_PWM_WIDTH+PULSE_COUNTER_WIDTH-1:FAST_PWM_WIDTH+FAST_PWM_WIDTH];

    phase_delay #(.WIDTH(SLOW_PWM_WIDTH),.FORWARD_DATA_WIDTH(FORWARD_DATA_WIDTH)) pd_1of4(
        .nrst(nrst),
        .clk(clk),
        .in_trig(trigger_odd_cycle | trigger_even_cycle),
        .delay(delay3_part1),
        .in_data_fwd({delay3_part234, npuls3, period3, duty3}),
        .out_trig(trigger_delay_1_to_2),
        .out_data_fwd(data_1_to_2)
    );

    phase_delay #(.WIDTH(SLOW_PWM_WIDTH),.FORWARD_DATA_WIDTH(FORWARD_DATA_WIDTH)) pd_2of4(
        .nrst(nrst),
        .clk(clk),
        .in_trig(trigger_delay_1_to_2),
        .delay(data_1_to_2[DELAY_BITS_POS+SLOW_PWM_WIDTH-1:DELAY_BITS_POS]),
        .in_data_fwd(data_1_to_2),
        .out_trig(trigger_delay_2_to_3),
        .out_data_fwd(data_2_to_3)
    );

    phase_delay #(.WIDTH(SLOW_PWM_WIDTH),.FORWARD_DATA_WIDTH(FORWARD_DATA_WIDTH)) pd_3of4(
        .nrst(nrst),
        .clk(clk),
        .in_trig(trigger_delay_2_to_3),
        .delay(data_2_to_3[DELAY_BITS_POS+SLOW_PWM_WIDTH-1:DELAY_BITS_POS]),
        .in_data_fwd(data_2_to_3),
        .out_trig(trigger_delay_3_to_4),
        .out_data_fwd(data_3_to_4)
    );

    phase_delay #(.WIDTH(SLOW_PWM_WIDTH),.FORWARD_DATA_WIDTH(FORWARD_DATA_WIDTH)) pd_4of4(
        .nrst(nrst),
        .clk(clk),
        .in_trig(trigger_delay_3_to_4),
        .delay(data_3_to_4[DELAY_BITS_POS+SLOW_PWM_WIDTH-1:DELAY_BITS_POS]),
        .in_data_fwd(data_3_to_4),
        .out_trig(trigger_delay_4_to_pulse_train),
        .out_data_fwd(data_4_to_gen)
    );

    pulse_train_gen #(.WIDTH(FAST_PWM_WIDTH),.PULSE_COUNTER_WIDTH(PULSE_COUNTER_WIDTH)) pwm3(
        .nrst(nrst),
        .clk(clk),
        .trigger(trigger_delay_4_to_pulse_train),
        .npuls(npuls3_gen),
        .period(period3_gen),
        .duty(duty3_gen),
        .pwm_out(Out3)
    );

endmodule

