// uart.v
// -----------------------------------------------------------------------------
// Clean request-triggered UART for the Colorlight OTTO PDP-8 integration.
//
// Same port list as the old OTTO uart.v.
// TX side:
//   - accepts tx_data when tx_req is asserted and the transmitter is free
//   - asserts tx_ack and keeps it high until tx_req goes low
//   - drives tx_empty low during the full UART frame
//   - drives tx_empty high again after the stop bit
//
// RX side:
//   - receives 8N1 asynchronous serial data from rx_in
//   - stores one received byte in rx_data
//   - drives rx_empty low while one byte is pending
//   - clears the pending byte when rx_req is asserted and returns rx_ack
//
// Baud note:
//   BAUD_DIV = 656 is the x4-core-clock value for the current OTTO
//   Colorlight clocking arrangement at a terminal setting of 2400 baud.
//   This is four times slower than the previous 9600 baud setting,
//   giving the slow FPGA PDP-8 core a much larger margin during
//   Binary Loader / paper-tape transfers.
// -----------------------------------------------------------------------------

`default_nettype none

module uart(
    clk, reset,
    tx_clk, tx_req, tx_ack, tx_data, tx_empty,
    rx_clk, rx_req, rx_ack, rx_start, rx_data, rx_empty,
    rx_in, tx_out
);

    input        clk;
    input        reset;

    input        tx_clk;
    input        rx_clk;

    input        tx_req;
    output reg   tx_ack;
    input  [7:0] tx_data;
    output reg   tx_empty;
    output reg   tx_out;

    input        rx_req;
    output reg   rx_ack;
    output reg   rx_start;   // one clk pulse when a new start bit is accepted
    output reg [7:0] rx_data;
    output reg   rx_empty;
    input        rx_in;

    // Presently unused. Kept to preserve the historical OTTO uart.v interface.
    wire unused_tx_clk = tx_clk;
    wire unused_rx_clk = rx_clk;

    localparam integer BAUD_DIV = 656;
    localparam integer HALF_DIV = 328;

    // -------------------------------------------------------------------------
    // Transmitter
    // -------------------------------------------------------------------------
    reg [15:0] tx_cnt;
    reg [3:0]  tx_bit;
    reg [9:0]  tx_frame;
    reg        tx_busy;
    reg        ack_hold;

    // -------------------------------------------------------------------------
    // Receiver
    // -------------------------------------------------------------------------
    reg        rx_meta;
    reg        rx_sync;
    reg [15:0] rx_cnt;
    reg [3:0]  rx_bit;
    reg [7:0]  rx_shift;
    reg [1:0]  rx_state;

    localparam RX_IDLE  = 2'd0;
    localparam RX_START = 2'd1;
    localparam RX_DATA  = 2'd2;
    localparam RX_STOP  = 2'd3;

    always @(posedge clk or posedge reset) begin
        if (reset) begin
            tx_out   <= 1'b1;
            tx_empty <= 1'b1;
            tx_ack   <= 1'b0;

            tx_busy  <= 1'b0;
            ack_hold <= 1'b0;
            tx_cnt   <= 16'd0;
            tx_bit   <= 4'd0;
            tx_frame <= 10'b1111111111;

            rx_ack   <= 1'b0;
            rx_start <= 1'b0;
            rx_data  <= 8'd0;
            rx_empty <= 1'b1;
            rx_meta  <= 1'b1;
            rx_sync  <= 1'b1;
            rx_cnt   <= 16'd0;
            rx_bit   <= 4'd0;
            rx_shift <= 8'd0;
            rx_state <= RX_IDLE;
        end else begin
            // -----------------------------------------------------------------
            // Synchronize asynchronous RX input.
            // -----------------------------------------------------------------
            rx_meta <= rx_in;
            rx_sync <= rx_meta;

            // -----------------------------------------------------------------
            // RX start pulse default.  This is used by the Teletype
            // reader-run relay: the reader is no longer enabled as soon
            // as the UART begins assembling a character.
            // -----------------------------------------------------------------
            rx_start <= 1'b0;

            // -----------------------------------------------------------------
            // RX acknowledge/consume handshake.
            // rx_data remains stable until the PDP-8 side asks to consume it.
            // -----------------------------------------------------------------
            rx_ack <= 1'b0;
            if (rx_req) begin
                rx_ack <= 1'b1;
                rx_empty <= 1'b1;
            end

            // -----------------------------------------------------------------
            // Receiver state machine.  One-byte buffer only: if a byte is still
            // pending, ignore further start bits until it has been consumed.
            // -----------------------------------------------------------------
            case (rx_state)
                RX_IDLE: begin
                    if (rx_empty && !rx_sync) begin
                        rx_start <= 1'b1;
                        rx_state <= RX_START;
                        rx_cnt   <= HALF_DIV;
                    end
                end

                RX_START: begin
                    if (rx_cnt != 0) begin
                        rx_cnt <= rx_cnt - 1'b1;
                    end else begin
                        // Validate start bit near its center.
                        if (!rx_sync) begin
                            rx_state <= RX_DATA;
                            rx_cnt   <= BAUD_DIV - 1;
                            rx_bit   <= 4'd0;
                        end else begin
                            rx_state <= RX_IDLE;
                        end
                    end
                end

                RX_DATA: begin
                    if (rx_cnt != 0) begin
                        rx_cnt <= rx_cnt - 1'b1;
                    end else begin
                        // LSB first.
                        rx_shift <= {rx_sync, rx_shift[7:1]};
                        rx_cnt   <= BAUD_DIV - 1;

                        if (rx_bit == 4'd7) begin
                            rx_state <= RX_STOP;
                        end else begin
                            rx_bit <= rx_bit + 1'b1;
                        end
                    end
                end

                RX_STOP: begin
                    if (rx_cnt != 0) begin
                        rx_cnt <= rx_cnt - 1'b1;
                    end else begin
                        // Accept only if stop bit is high.  Framing errors are
                        // silently discarded for this first hardware test.
                        if (rx_sync) begin
                            rx_data  <= rx_shift;
                            rx_empty <= 1'b0;
                        end
                        rx_state <= RX_IDLE;
                    end
                end

                default: begin
                    rx_state <= RX_IDLE;
                end
            endcase

            // -----------------------------------------------------------------
            // Acknowledge protocol expected by Teletype.v:
            // tx_ack resets the tx_load_req SR latch. Holding it until tx_req
            // falls makes the handshake insensitive to clk/clk_n phase details.
            // -----------------------------------------------------------------
            if (ack_hold) begin
                tx_ack <= 1'b1;

                if (!tx_req) begin
                    ack_hold <= 1'b0;
                    tx_ack   <= 1'b0;
                end
            end else begin
                tx_ack <= 1'b0;
            end

            if (!tx_busy) begin
                tx_out   <= 1'b1;
                tx_empty <= 1'b1;

                if (tx_req && !ack_hold) begin
                    // UART frame: start bit, 8 data bits LSB first, stop bit.
                    tx_frame <= {1'b1, tx_data, 1'b0};
                    tx_out   <= 1'b0;              // start bit immediately
                    tx_empty <= 1'b0;
                    tx_busy  <= 1'b1;
                    tx_bit   <= 4'd0;
                    tx_cnt   <= BAUD_DIV - 1;

                    ack_hold <= 1'b1;
                    tx_ack   <= 1'b1;
                end
            end else begin
                tx_empty <= 1'b0;

                if (tx_cnt != 0) begin
                    tx_cnt <= tx_cnt - 1'b1;
                end else begin
                    tx_cnt <= BAUD_DIV - 1;

                    if (tx_bit == 4'd9) begin
                        tx_out   <= 1'b1;          // idle after stop bit
                        tx_empty <= 1'b1;
                        tx_busy  <= 1'b0;
                        tx_bit   <= 4'd0;
                    end else begin
                        tx_bit <= tx_bit + 1'b1;
                        tx_out <= tx_frame[tx_bit + 1'b1];
                    end
                end
            end
        end
    end

endmodule

`default_nettype wire
