/**********************************************************
 * 
 * USB Video packet sender
 * (c) Bigscreen, Inc. 2024
 * Created by: David Miller
 *
 * Sends USB Video Class (UVC) data packets containing 
 * video frame data.
 * Interfaces with a frame buffer to pull captured video
 * data and forward it to USB.
 *
 *********************************************************/

module video_fifo
#(
    parameter VIDEO_STREAM_ENDPOINT = 4'h01,
    parameter MAX_PACKET_LEN = 12'd64,
    parameter MAX_PAYLOAD_SIZE = 12'd64 // note that payload size is defined as header + data
)
(
    input clk, 
    input areset, // async active high reset

    // USB Device controller interface
    input txact, // transmission active, holds high for the duration of a packet
    input txpop, // tells us to prepare the next byte
    input pktfin, // end of this tx packet
    output [7:0] txdat, // data to write over USB
    output txcork, // assert high to indicate no data is ready
    output [11:0] txdat_len, // how many bytes are we writing?

    // Video buffer interface
    input [7:0] video_buffer_data, // byte data from the video buffer
    input [15:0] video_frame_length, // length in bytes of this frame's buffer. 
                                     // Should be held same value through entire frame readout.
    input frame_ready, // true when there is valid data to send
    output buffer_read_clock,
    output [15:0] buffer_address, // not sure if 16 bits is required, but it will be truncated anyway
    output frame_complete // set true for one cycle when reading the last byte (or after)

);

localparam HEADER_SIZE = 'd2;

wire empty; // true when we are all out of data to send.
reg [7:0] packet_byte_count; // read pointer for this packet, resets to zero on each packet
reg [15:0] bytes_remaining; // counter for the buffer read address, how far in the frame we have sent
reg [15:0] buf_ptr; // output address to read from the frame buffer

reg uvc_eof; // end of frame, sent in the payload header
reg uvc_frame_id; // frame id (toggles 0 or 1), sent in the payload header


assign buffer_address = buf_ptr;
assign buffer_read_clock = clk;

reg [1:0] vps_state;
localparam      VPS_STATE_IDLE = 'd0,
                VPS_STATE_SENDING = 'd1,
                VPS_STATE_FINISHED = 'd2;

always @(posedge clk or posedge areset) begin
    if(areset) begin
        vps_state <= VPS_STATE_IDLE;
    end else begin
        case(vps_state)
        VPS_STATE_IDLE: begin
            if(frame_ready)
                vps_state <= VPS_STATE_SENDING;
        end
        VPS_STATE_SENDING: begin
            if(empty)
                vps_state <= VPS_STATE_FINISHED;
        end
        VPS_STATE_FINISHED: begin
            if(~frame_ready)
                vps_state <= VPS_STATE_IDLE;
        end
        endcase
    end
end

assign frame_complete = (vps_state == VPS_STATE_FINISHED);

// start a packet on frame_ready
always @(posedge clk or posedge areset) begin
    if(areset) begin
        uvc_eof <= 1'b0;
        uvc_frame_id <= 1'b0;
        bytes_remaining <= 16'b0;
    end else begin
        if(frame_ready && (vps_state == VPS_STATE_IDLE)) begin
            uvc_frame_id <= ~uvc_frame_id;
            bytes_remaining <= video_frame_length;
        end 
        if(pktfin) begin  // end of this packet / payload
            if(bytes_remaining > (MAX_PACKET_LEN - HEADER_SIZE)) begin
                bytes_remaining <= bytes_remaining - (MAX_PACKET_LEN - HEADER_SIZE);
            end else begin
                bytes_remaining <= 12'd0;
            end
        end
        if(bytes_remaining <= (MAX_PACKET_LEN - HEADER_SIZE))
            uvc_eof <= 1'b1;
        else
            uvc_eof <= 1'b0;
    end
end

// data selection - either from the header or real frame data
reg [7:0] my_txdat;
assign txdat = my_txdat;
always @(posedge clk or posedge areset) begin
    if(areset) begin
        packet_byte_count <= 8'd0;
        my_txdat <= 8'd0;
    end else begin
        if(~txact) begin
            packet_byte_count <= 8'd0;
        end else begin
            if(~empty) begin
                if(packet_byte_count < HEADER_SIZE) begin
                    // my_txdat <= uvc_payload_header[packet_byte_count];
                    if(packet_byte_count == 8'd0)       my_txdat <= HEADER_SIZE;
                    else if (packet_byte_count == 8'd1) my_txdat <= {6'h00, uvc_eof, uvc_frame_id};
                end else begin
                    my_txdat <= video_buffer_data; // fed with address = packet_offset + packet_byte_count - 2 
                end
                // progression through packet done by txpop
                if(txpop) begin
                    packet_byte_count <= packet_byte_count + 8'd1;
                end
            end 
        end
    end
end

// buffer read pointer
always @(posedge clk or posedge areset) begin
    if(areset) begin
        buf_ptr <= 'd0;
    end else begin
        if(packet_byte_count >= HEADER_SIZE) begin
            buf_ptr <= video_frame_length - bytes_remaining + {8'b0, packet_byte_count} - HEADER_SIZE;
        end else begin
            buf_ptr <= 'd0;
        end
    end
end

// packet length for this packet
assign txdat_len = empty ? 12'd0 : 
    (bytes_remaining > (MAX_PACKET_LEN - HEADER_SIZE)) ? MAX_PACKET_LEN : HEADER_SIZE + bytes_remaining[11:0];

// if we are empty or not
assign empty = (bytes_remaining == 12'd0);
assign txcork = empty;


endmodule
