/*****************************************************************************
 *                                                                           *
 * Module:       Altera_UP_PS2                                               *
 * Description:                                                              *
 *      This module communicates with the PS2 core.                          *
 *                                                                           *
 * www.eecg.toronto.edu/~jayar/ece241_08F/AudioVideoCores/ps2/ps2.html       *
 *****************************************************************************/

module ps2controller #(parameter INITIALIZE_MOUSE = 0) (

    // Inputs
    CLOCK_50,
    reset,

    the_command,
    send_command,

    // Bidirectionals
    PS2_CLK,                    // PS2 Clock
    PS2_DAT,                    // PS2 Data

    // Outputs
    command_was_sent,
    error_communication_timed_out,

    received_data,
    received_data_en            // If 1 - new data has been received
);

/*****************************************************************************
 *                             Port Declarations                             *
 *****************************************************************************/

// Inputs
input           CLOCK_50;
input           reset;
input    [7:0]  the_command;
input           send_command;

// Bidirectionals
inout           PS2_CLK;
inout           PS2_DAT;

// Outputs
output          command_was_sent;
output          error_communication_timed_out;

output   [7:0]  received_data;
output          received_data_en;

// ---------------------------------------------------------------------
wire     [7:0]  the_command_w;
wire            send_command_w,
                command_was_sent_w,
                error_communication_timed_out_w;

generate

    if (INITIALIZE_MOUSE) begin

        assign the_command_w        = init_done ? the_command : 8'hf4;
        assign send_command_w       = init_done ? send_command : (!command_was_sent_w && !error_communication_timed_out_w);
        assign command_was_sent     = init_done ? command_was_sent_w : 0;
        assign error_communication_timed_out = init_done ? error_communication_timed_out_w : 1;

        reg init_done;

        always @(posedge CLOCK_50)

            if (reset) init_done <= 0;
            else if (command_was_sent_w) init_done <= 1;

    end else
    begin

        assign the_command_w    = the_command;
        assign send_command_w   = send_command;
        assign command_was_sent = command_was_sent_w;
        assign error_communication_timed_out = error_communication_timed_out_w;

    end

endgenerate

/*****************************************************************************
 *                           Constant Declarations                           *
 *****************************************************************************/
// states
localparam
    PS2_STATE_0_IDLE                = 3'h0,
    PS2_STATE_1_DATA_IN             = 3'h1,
    PS2_STATE_2_COMMAND_OUT         = 3'h2,
    PS2_STATE_3_END_TRANSFER        = 3'h3,
    PS2_STATE_4_END_DELAYED         = 3'h4;

/*****************************************************************************
 *                 Internal wires and registers Declarations                 *
 *****************************************************************************/

// Internal Wires
wire    ps2_clk_posedge;
wire    ps2_clk_negedge;
wire    start_receiving_data;
wire    wait_for_incoming_data;

// Internal Registers
reg        [7:0]    idle_counter;

reg                 ps2_clk_reg;
reg                 ps2_data_reg;
reg                 last_ps2_clk;

// State Machine Registers
reg        [2:0]    ns_ps2_transceiver;
reg        [2:0]    s_ps2_transceiver;

/*****************************************************************************
 *                         Finite State Machine(s)                           *
 *****************************************************************************/

always @(posedge CLOCK_50)
begin

    if (reset == 1'b1)
        s_ps2_transceiver <= PS2_STATE_0_IDLE;
    else
        s_ps2_transceiver <= ns_ps2_transceiver;
end

always @(*)
begin

    // Defaults
    ns_ps2_transceiver = PS2_STATE_0_IDLE;

    case (s_ps2_transceiver)

        /* Ожидание */
        PS2_STATE_0_IDLE:

            begin

                if ((idle_counter == 8'hFF) && (send_command == 1'b1))
                    ns_ps2_transceiver = PS2_STATE_2_COMMAND_OUT;

                else if ((ps2_data_reg == 1'b0) && (ps2_clk_posedge == 1'b1))
                    ns_ps2_transceiver = PS2_STATE_1_DATA_IN;

                else
                    ns_ps2_transceiver = PS2_STATE_0_IDLE;
            end

        /* Получение данных */
        PS2_STATE_1_DATA_IN:

            begin

                if ((received_data_en == 1'b1)/* && (ps2_clk_posedge == 1'b1)*/)
                    ns_ps2_transceiver = PS2_STATE_0_IDLE;
                else
                    ns_ps2_transceiver = PS2_STATE_1_DATA_IN;
            end

        /* Команда */
        PS2_STATE_2_COMMAND_OUT:

            begin
                if ((command_was_sent == 1'b1) || (error_communication_timed_out == 1'b1))
                     ns_ps2_transceiver = PS2_STATE_3_END_TRANSFER;
                else ns_ps2_transceiver = PS2_STATE_2_COMMAND_OUT;
            end

        /* Завершение передачи */
        PS2_STATE_3_END_TRANSFER:

            begin
                if (send_command == 1'b0)
                    ns_ps2_transceiver = PS2_STATE_0_IDLE;

                else if ((ps2_data_reg == 1'b0) && (ps2_clk_posedge == 1'b1))
                    ns_ps2_transceiver = PS2_STATE_4_END_DELAYED;

                else
                    ns_ps2_transceiver = PS2_STATE_3_END_TRANSFER;
            end

        /* Завершение передачи с задержкой */
        PS2_STATE_4_END_DELAYED:

            begin

                if (received_data_en == 1'b1)
                begin

                    if (send_command == 1'b0)
                        ns_ps2_transceiver = PS2_STATE_0_IDLE;
                    else
                        ns_ps2_transceiver = PS2_STATE_3_END_TRANSFER;
                end
                else
                    ns_ps2_transceiver = PS2_STATE_4_END_DELAYED;
            end

        /* Все другие статусы */
        default:
            ns_ps2_transceiver = PS2_STATE_0_IDLE;

    endcase
end

/*****************************************************************************
 *                             Sequential logic                              *
 *****************************************************************************/

always @(posedge CLOCK_50)
begin

    if (reset == 1'b1)
    begin

        last_ps2_clk    <= 1'b1;
        ps2_clk_reg     <= 1'b1;
        ps2_data_reg    <= 1'b1;

    end
    else
    begin

        last_ps2_clk    <= ps2_clk_reg;
        ps2_clk_reg     <= PS2_CLK;
        ps2_data_reg    <= PS2_DAT;

    end
end

always @(posedge CLOCK_50)
begin

    if (reset == 1'b1)
        idle_counter <= 6'h00;

    else if ((s_ps2_transceiver == PS2_STATE_0_IDLE) && (idle_counter != 8'hFF))
        idle_counter <= idle_counter + 6'h01;

    else if (s_ps2_transceiver != PS2_STATE_0_IDLE)
        idle_counter <= 6'h00;
end

/*****************************************************************************
 *                            Combinational logic                            *
 *****************************************************************************/

assign ps2_clk_posedge         = ((ps2_clk_reg == 1'b1) && (last_ps2_clk == 1'b0)) ? 1'b1 : 1'b0;
assign ps2_clk_negedge         = ((ps2_clk_reg == 1'b0) && (last_ps2_clk == 1'b1)) ? 1'b1 : 1'b0;
assign start_receiving_data    = (s_ps2_transceiver == PS2_STATE_1_DATA_IN);
assign wait_for_incoming_data  = (s_ps2_transceiver == PS2_STATE_3_END_TRANSFER);

/*****************************************************************************
 *                              Internal Modules                             *
 *****************************************************************************/

Altera_UP_PS2_Data_In PS2_Data_In
(
    // Inputs
    .clk                        (CLOCK_50),
    .reset                      (reset),

    .wait_for_incoming_data     (wait_for_incoming_data),
    .start_receiving_data       (start_receiving_data),

    .ps2_clk_posedge            (ps2_clk_posedge),
    .ps2_clk_negedge            (ps2_clk_negedge),
    .ps2_data                   (ps2_data_reg),

    // Bidirectionals Outputs
    .received_data              (received_data),
    .received_data_en           (received_data_en)
);

Altera_UP_PS2_Command_Out PS2_Command_Out (

    // Inputs
    .clk                (CLOCK_50),
    .reset              (reset),
    .the_command        (the_command_w),
    .send_command       (send_command_w),
    .ps2_clk_posedge    (ps2_clk_posedge),
    .ps2_clk_negedge    (ps2_clk_negedge),

    // Bidirectionals
    .PS2_CLK            (PS2_CLK),
     .PS2_DAT           (PS2_DAT),

    // Outputs
    .command_was_sent               (command_was_sent_w),
    .error_communication_timed_out  (error_communication_timed_out_w)
);

endmodule