/*
* Copyright 2018-2022 F4PGA Authors
*
* 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
*/

module ERROR_OUTPUT_LOGIC #(
    parameter [7:0] DATA_WIDTH = 1,
    parameter [7:0] ADDR_WIDTH = 6
) (
    input rst,
    input clk,

    input loop_complete,
    input error_detected,
    input [7:0] error_state,
    input [ADDR_WIDTH-1:0] error_address,
    input [DATA_WIDTH-1:0] expected_data,
    input [DATA_WIDTH-1:0] actual_data,

    // Output to UART
    input tx_data_accepted,
    output reg tx_data_ready,
    output reg [7:0] tx_data
);
    reg reg_error_detected;
    reg [7:0] reg_error_state;
    reg [ADDR_WIDTH-1:0] reg_error_address;
    reg [DATA_WIDTH-1:0] reg_expected_data;
    reg [DATA_WIDTH-1:0] reg_actual_data;

    reg [7:0] error_count;
    reg [7:0] output_shift;

    wire [7:0] next_output_shift = output_shift + 8;
    wire count_shift_done = next_output_shift >= 8'd16;
    wire address_shift_done = next_output_shift >= ADDR_WIDTH;
    wire data_shift_done = next_output_shift >= DATA_WIDTH;

    reg loop_ready;
    reg [7:0] latched_error_count;

    reg [7:0] errors;
    reg [10:0] state;
    reg [15:0] loop_count;
    reg [15:0] latched_loop_count;

    localparam START = (1 << 0),
        ERROR_COUNT_HEADER = (1 << 1),
        ERROR_COUNT_COUNT = (1 << 2),
        CR = (1 << 3),
        LF = (1 << 4),
        ERROR_HEADER = (1 << 5),
        ERROR_STATE = (1 << 6),
        ERROR_ADDRESS = (1 << 7),
        ERROR_EXPECTED_DATA = (1 << 8),
        ERROR_ACTUAL_DATA = (1 << 9),
        LOOP_COUNT = (1 << 10);

    initial begin
        tx_data_ready <= 1'b0;
        tx_data <= 8'b0;
        state <= START;
        reg_error_detected <= 1'b0;
    end

    always @(posedge clk) begin
        if(rst) begin
            state <= START;
            error_count <= 0;
            reg_error_detected <= 0;
            tx_data_ready <= 0;
            tx_data <= 8'b0;
            loop_count <= 0;
            loop_ready <= 0;
        end else begin

            if(error_detected) begin
                if(error_count < 255) begin
                    error_count <= error_count + 1;
                end

                if(!reg_error_detected) begin
                    reg_error_detected <= 1;
                    reg_error_state <= error_state;
                    reg_error_address <= error_address;
                    reg_expected_data <= expected_data;
                    reg_actual_data <= actual_data;
                end
            end

            if(tx_data_accepted) begin
                tx_data_ready <= 0;
            end

            if(loop_complete) begin
                loop_count <= loop_count + 1;
                if(!loop_ready) begin
                    loop_ready <= 1;
                    latched_error_count <= error_count;
                    latched_loop_count <= loop_count;
                    error_count <= 0;
                end
            end

            case(state)
                START: begin
                    if(reg_error_detected) begin
                        state <= ERROR_HEADER;
                    end else if(loop_ready) begin
                        state <= ERROR_COUNT_HEADER;
                    end
                end
                ERROR_COUNT_HEADER: begin
                    if(!tx_data_ready) begin
                        tx_data <= "L";
                        tx_data_ready <= 1;
                        state <= ERROR_COUNT_COUNT;
                    end
                end
                ERROR_COUNT_COUNT: begin
                    if(!tx_data_ready) begin
                        tx_data <= latched_error_count;
                        tx_data_ready <= 1;
                        output_shift <= 0;
                        state <= LOOP_COUNT;
                    end
                end
                LOOP_COUNT: begin
                    if(!tx_data_ready) begin
                        tx_data <= (latched_loop_count >> output_shift);
                        tx_data_ready <= 1;

                        if(count_shift_done) begin
                            output_shift <= 0;
                            loop_ready <= 0;
                            state <= CR;
                        end else begin
                            output_shift <= next_output_shift;
                        end
                    end
                end
                CR: begin
                    if(!tx_data_ready) begin
                        tx_data <= 8'h0D; // "\r"
                        tx_data_ready <= 1;
                        state <= LF;
                    end
                end
                LF: begin
                    if(!tx_data_ready) begin
                        tx_data <= 8'h0A; // "\n"
                        tx_data_ready <= 1;
                        state <= START;
                    end
                end
                ERROR_HEADER: begin
                    if(!tx_data_ready) begin
                        tx_data <= "E";
                        tx_data_ready <= 1;
                        state <= ERROR_STATE;
                    end
                end
                ERROR_STATE: begin
                    if(!tx_data_ready) begin
                        tx_data <= reg_error_state;
                        tx_data_ready <= 1;
                        output_shift <= 0;
                        state <= ERROR_ADDRESS;
                    end
                end
                ERROR_ADDRESS: begin
                    if(!tx_data_ready) begin
                        tx_data <= (reg_error_address >> output_shift);
                        tx_data_ready <= 1;

                        if(address_shift_done) begin
                            output_shift <= 0;
                            state <= ERROR_EXPECTED_DATA;
                        end else begin
                            output_shift <= next_output_shift;
                        end
                    end
                end
                ERROR_EXPECTED_DATA: begin
                    if(!tx_data_ready) begin
                        tx_data <= (reg_expected_data >> output_shift);
                        tx_data_ready <= 1;

                        if(data_shift_done) begin
                            output_shift <= 0;
                            state <= ERROR_ACTUAL_DATA;
                        end else begin
                            output_shift <= next_output_shift;
                        end
                    end
                end
                ERROR_ACTUAL_DATA: begin
                    if(!tx_data_ready) begin
                        tx_data <= (reg_actual_data >> output_shift);
                        tx_data_ready <= 1;

                        if(data_shift_done) begin
                            state <= CR;
                            reg_error_detected <= 0;
                        end else begin
                            output_shift <= output_shift + 8;
                        end
                    end
                end
                default: begin
                    state <= START;
                end
            endcase
        end
    end
endmodule
