// Copyright (c) 2020, Andrew Kay
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

`default_nettype none

module coax_buffered_tx (
    input clk,
    input reset,
    output active,
    output tx,
    input [9:0] data,
    input load_strobe,
    input start_strobe,
    output empty,
    output full,
    output reg ready,
    input parity
);
    parameter CLOCKS_PER_BIT = 8;
    parameter DEPTH = 256;
    parameter START_DEPTH = DEPTH * 0.75;

    localparam STATE_IDLE = 0;
    localparam STATE_TRANSMITTING_1 = 1;
    localparam STATE_TRANSMITTING_2 = 2;
    localparam STATE_TRANSMITTING_3 = 3;

    reg [1:0] state = STATE_IDLE;
    reg [1:0] next_state;

    reg next_ready;

    wire [9:0] coax_tx_data;
    reg coax_tx_strobe = 0;
    reg next_coax_tx_strobe;
    wire coax_tx_ready;

    coax_tx #(
        .CLOCKS_PER_BIT(CLOCKS_PER_BIT)
    ) coax_tx (
        .clk(clk),
        .reset(reset),
        .active(active),
        .tx(tx),
        .data(coax_tx_data),
        .strobe(coax_tx_strobe),
        .ready(coax_tx_ready),
        .parity(parity)
    );

    reg coax_buffer_read_strobe = 0;
    reg next_coax_buffer_read_strobe;
    wire coax_buffer_almost_full;

    coax_buffer #(
        .DEPTH(DEPTH),
        .ALMOST_FULL_THRESHOLD(START_DEPTH)
    ) coax_buffer (
        .clk(clk),
        .reset(reset),
        .write_data(data),
        .write_strobe(load_strobe),
        .read_data(coax_tx_data),
        .read_strobe(coax_buffer_read_strobe),
        .empty(empty),
        .almost_full(coax_buffer_almost_full),
        .full(full)
    );

    always @(*)
    begin
        next_state = state;

        next_coax_tx_strobe = 0;
        next_coax_buffer_read_strobe = 0;

        next_ready = 1;

        case (state)
            STATE_IDLE:
            begin
                // NOTE: Redundant check of almost full AND not empty is in
                // order to protect against bugs with the almost full logic.
                if ((start_strobe || coax_buffer_almost_full) && !empty)
                    next_state = STATE_TRANSMITTING_1;
            end

            STATE_TRANSMITTING_1:
            begin
                if (coax_tx_ready)
                begin
                    if (!empty)
                    begin
                        next_coax_tx_strobe = 1;
                        next_state = STATE_TRANSMITTING_2;
                    end
                    else
                    begin
                        next_ready = 0;
                        next_state = STATE_TRANSMITTING_3;
                    end
                end
            end

            STATE_TRANSMITTING_2:
            begin
                next_coax_buffer_read_strobe = 1;
                next_state = STATE_TRANSMITTING_1;
            end

            STATE_TRANSMITTING_3:
            begin
                next_ready = 0;

                if (!active)
                    next_state = STATE_IDLE;
            end
        endcase
    end

    always @(posedge clk)
    begin
        state <= next_state;

        coax_tx_strobe <= next_coax_tx_strobe;
        coax_buffer_read_strobe <= next_coax_buffer_read_strobe;

        ready <= next_ready;

        if (reset)
        begin
            state <= STATE_IDLE;

            coax_tx_strobe <= 0;
            coax_buffer_read_strobe <= 0;

            ready <= 1;
        end
    end
endmodule
