// SPDX-License-Identifier: CERN-OHL-S-2.0
/*

Copyright (c) 2020-2025 FPGA Ninja, LLC

Authors:
- Alex Forencich

*/

`resetall
`timescale 1ns / 1ps
`default_nettype none

/*
 * LED shift register driver
 */
module taxi_led_sreg #(
    // number of LEDs
    parameter COUNT = 8,
    // invert output
    parameter logic INVERT = 1'b0,
    // reverse order
    parameter logic REVERSE = 1'b0,
    // interleave A and B inputs, otherwise only use A
    parameter logic INTERLEAVE = 1'b0,
    // clock prescale
    parameter PRESCALE = 31
)
(
    input  wire logic              clk,
    input  wire logic              rst,

    input  wire logic [COUNT-1:0]  led_a,
    input  wire logic [COUNT-1:0]  led_b,

    output wire logic              sreg_d,
    output wire logic              sreg_ld,
    output wire logic              sreg_clk
);

localparam COUNT_INT = INTERLEAVE ? COUNT*2 : COUNT;
localparam CL_COUNT = $clog2(COUNT_INT);
localparam CL_PRESCALE = $clog2(PRESCALE+1);

logic [CL_COUNT+1-1:0] count_reg = 0;
logic [CL_PRESCALE-1:0] prescale_count_reg = 0;
logic enable_reg = 1'b0;
logic update_reg = 1'b1;
logic cycle_reg = 1'b0;

logic [COUNT_INT-1:0] led_sync_reg_1 = 0;
logic [COUNT_INT-1:0] led_sync_reg_2 = 0;
logic [COUNT_INT-1:0] led_reg = 0;

logic sreg_d_reg = 1'b0;
logic sreg_ld_reg = 1'b0;
logic sreg_clk_reg = 1'b0;

assign sreg_d = INVERT ? !sreg_d_reg : sreg_d_reg;
assign sreg_ld = sreg_ld_reg;
assign sreg_clk = sreg_clk_reg;

wire [COUNT_INT-1:0] led_in;
wire [COUNT_INT-1:0] led_sync;

if (INTERLEAVE) begin
    for (genvar i = 0; i < COUNT; i = i + 1) begin
        assign led_in[i*2 +: 2] = {led_b[i], led_a[i]};
    end
end else begin
    assign led_in = led_a;
end

taxi_sync_signal #(
    .WIDTH(COUNT_INT),
    .N(2)
)
sync_inst (
    .clk(clk),
    .in(led_in),
    .out(led_sync)
);

always @(posedge clk) begin
    enable_reg <= 1'b0;

    if (prescale_count_reg != 0) begin
        prescale_count_reg <= prescale_count_reg - 1;
    end else begin
        enable_reg <= 1'b1;
        prescale_count_reg <= PRESCALE;
    end

    if (enable_reg) begin
        if (cycle_reg) begin
            cycle_reg <= 1'b0;
            sreg_clk_reg <= 1'b1;
        end else if (count_reg != 0) begin
            sreg_clk_reg <= 1'b0;
            sreg_ld_reg <= 1'b0;

            if (count_reg < COUNT_INT) begin
                count_reg <= count_reg + 1;
                cycle_reg <= 1'b1;
                if (REVERSE) begin
                    sreg_d_reg <= led_reg[CL_COUNT'(COUNT_INT-1-count_reg)];
                end else begin
                    sreg_d_reg <= led_reg[CL_COUNT'(count_reg)];
                end
            end else begin
                count_reg <= 0;
                cycle_reg <= 1'b0;
                sreg_d_reg <= 1'b0;
                sreg_ld_reg <= 1'b1;
            end
        end else begin
            sreg_clk_reg <= 1'b0;
            sreg_ld_reg <= 1'b0;

            if (update_reg) begin
                update_reg <= 1'b0;

                count_reg <= 1;
                cycle_reg <= 1'b1;
                if (REVERSE) begin
                    sreg_d_reg <= led_reg[COUNT_INT-1];
                end else begin
                    sreg_d_reg <= led_reg[0];
                end
            end
        end
    end

    if (led_sync != led_reg) begin
        led_reg <= led_sync;
        update_reg <= 1'b1;
    end

    if (rst) begin
        count_reg <= 0;
        prescale_count_reg <= 0;
        enable_reg <= 1'b0;
        update_reg <= 1'b1;
        cycle_reg <= 1'b0;
        led_reg <= 0;
        sreg_d_reg <= 1'b0;
        sreg_ld_reg <= 1'b0;
        sreg_clk_reg <= 1'b0;
    end
end

endmodule

`resetall
