
module big_ila_usb_xmit
#(
    parameter BI_DATA_WIDTH = 16, // number of bits to capture
    parameter MEM_WORD_WIDTH = 9,  // number of samples to capture = 2^MEM_WORD_WIDTH
    parameter MAX_PACKET_SIZE = 512
)
(
    input   CLK
,   input   RESET

    // interface to ILA monitor
,   input   CAPTURE_DONE
,   input   [BI_DATA_WIDTH-1:0] DATA_IN
,   input   [MEM_WORD_WIDTH-1:0] TRIG_POINT
,   output  DATA_READ_ENABLE

    // interface to Gowin USB device controller
,   input   TXACT // transmitting is active (received IN packet)
,   input   TXPOP // reading the data on TXDAT
,   output  [7:0] TXDAT // data to transmit
,   output  [11:0] TXDAT_LEN // number of bytes to send
,   output  TXCORK // high indicates a NAK
);

localparam HEADER_SIZE = 2; 
// Data header:
//      byte 0: length of header (2)
//      byte 1: packet type. 0 = trigger point, 1 = data

localparam NUM_TRIG_BYTES = (MEM_WORD_WIDTH + 7) / 8;
localparam NUM_DATA_BYTES = (BI_DATA_WIDTH + 7) / 8; // for sending a single sample
localparam TRIG_COUNT_WIDTH = $clog2(NUM_TRIG_BYTES);
localparam DATA_COUNT_WIDTH = $clog2(NUM_DATA_BYTES);
localparam TOTAL_DATA_COUNT = NUM_DATA_BYTES * (2**MEM_WORD_WIDTH); // total number of bytes to send
localparam TOTAL_DATA_WIDTH = $clog2(TOTAL_DATA_COUNT);

localparam MAX_SAMPLES = (MAX_PACKET_SIZE-'d2) / NUM_DATA_BYTES;
localparam MAX_DATA = MAX_SAMPLES * NUM_DATA_BYTES;

reg [DATA_COUNT_WIDTH-1:0] data_byte_count;
reg [TOTAL_DATA_WIDTH:0] total_data_byte_count;

localparam  BI_USB_STATE_IDLE = 'd0,
            BI_USB_STATE_SENDING_TRIG = 'd1,
            BI_USB_STATE_SENDING_DATA = 'd2,
            BI_USB_STATE_DONE = 'd3;

reg [1:0] bi_usb_state;

reg [11:0] packet_tx_bytes;

reg [(8*NUM_TRIG_BYTES)-1:0] trig_holding;
reg [(8*NUM_DATA_BYTES)-1:0] data_holding;

reg tx_was_active;
reg initial_data_latch;

reg [7:0] tx_dat;
reg [11:0] tx_len;


// function automatic [7:0] select_trig_holding(input [(8*NUM_TRIG_BYTES)-1:0] in_trig_holding, input [11:0] in_sel);
    
//     if(in_sel >= NUM_TRIG_BYTES)
//         select_trig_holding = 8'd0;
//     else begin
//         integer k;
//         for (k=0; k < 8; k = k + 1)
//             select_trig_holding[k] = in_trig_holding[8*in_sel + k];
//     end
// endfunction

// function automatic [7:0] select_data_holding(input [(8*NUM_DATA_BYTES)-1:0] in_data_holding, input [(DATA_COUNT_WIDTH-1):0] in_sel);
    
//     if(in_sel >= NUM_DATA_BYTES)
//         select_data_holding = 8'd0;
//     else begin
//         integer k;
//         for (k=0; k < 8; k = k + 1)
//             select_data_holding[k] = in_data_holding[8*in_sel + k];
//     end
// endfunction

// function automatic [11:0] calculate_packet_size(input [TOTAL_DATA_WIDTH:0] data_bytes_sent);
//     integer k = (MAX_PACKET_SIZE-'d2) / NUM_DATA_BYTES;
//     integer max_data = NUM_DATA_BYTES * k;
//     if((TOTAL_DATA_COUNT - data_bytes_sent) > max_data)
//         calculate_packet_size = max_data;
//     else
//         calculate_packet_size = (TOTAL_DATA_COUNT - data_bytes_sent);

// endfunction

always @(posedge CLK or posedge RESET) begin
    if(RESET) begin
        bi_usb_state <= BI_USB_STATE_IDLE;
        trig_holding <= 'd0;
        data_holding <= 'd0;
        data_byte_count <= 'd0;
        total_data_byte_count <= 'd0;
        packet_tx_bytes <= 'd0;
        tx_was_active <= 1'b0;
        initial_data_latch <= 1'b0;

        tx_dat <= 8'h0;
        tx_len <= 12'd0;
    end
    else begin
        case(bi_usb_state)
        BI_USB_STATE_IDLE: begin 
            if(CAPTURE_DONE) begin
                bi_usb_state <= BI_USB_STATE_SENDING_TRIG;
                trig_holding <= {{((8*NUM_TRIG_BYTES)-MEM_WORD_WIDTH){1'b0}}, TRIG_POINT};
                packet_tx_bytes <= 'd0;
                tx_dat <= HEADER_SIZE;
                initial_data_latch <= 1'b1;
            end
        end
        BI_USB_STATE_SENDING_TRIG: begin 
            if(initial_data_latch) begin
                initial_data_latch <= 1'b0;
                data_holding <= {{((8*NUM_DATA_BYTES)-BI_DATA_WIDTH){1'b0}}, DATA_IN};
            end
            
            tx_len <= NUM_TRIG_BYTES + HEADER_SIZE;
            

            if(TXACT) begin
                tx_was_active <= 1'b1;
                if(TXPOP) begin
                    packet_tx_bytes <= packet_tx_bytes + 'd1;
                    if(packet_tx_bytes == 'd0)
                        tx_dat <= 8'd0; // packet type: trigger point
                    else if((packet_tx_bytes - 'd1) >= NUM_TRIG_BYTES)
                        tx_dat <= 8'd0;
                    else
                        tx_dat <= trig_holding[((packet_tx_bytes - 'd1)*8)+:8];
                        // tx_dat <= select_trig_holding(trig_holding, packet_tx_bytes - 'd1);

                end
            end
            else begin
                // falling edge detection
                if(tx_was_active) begin
                    tx_was_active <= 1'b0;
                    if(packet_tx_bytes == tx_len) begin
                        bi_usb_state <= BI_USB_STATE_SENDING_DATA;
                        packet_tx_bytes <= 'd0;
                        data_byte_count <= 'd0;
                        total_data_byte_count <= 'd0;
                        tx_was_active <= 1'b0;
                        tx_dat <= HEADER_SIZE;
                    end
                end
            end
        end
        BI_USB_STATE_SENDING_DATA: begin 
            if(DATA_READ_ENABLE && CAPTURE_DONE) begin
                data_holding <= {{((8*NUM_DATA_BYTES)-BI_DATA_WIDTH){1'b0}}, DATA_IN};
            end
            // tx_len <= calculate_packet_size(total_data_byte_count) + 'd2;
            if((TOTAL_DATA_COUNT - total_data_byte_count) > MAX_DATA)
                tx_len <=  MAX_DATA + 'd2;
            else
                tx_len <= (TOTAL_DATA_COUNT - total_data_byte_count) + 'd2;
            
            if(TXACT) begin
                tx_was_active <= 1'b1;
                if(TXPOP) begin
                    packet_tx_bytes  <= packet_tx_bytes + 'd1;
                    if(packet_tx_bytes == 'd0)
                        tx_dat <= 8'd1; // packet type: data samples
                    else begin
                        // tx_dat <= select_data_holding(data_holding, data_byte_count);
                        if(data_byte_count >= NUM_DATA_BYTES)
                            tx_dat <= 8'd0;
                        else
                            tx_dat <= data_holding[(data_byte_count*8)+:8];
                        if(data_byte_count == (NUM_DATA_BYTES - 1)) begin
                            // data_latch_empty <= 1'b1;
                            data_byte_count <= 'd0;
                        end else begin
                            data_byte_count <= data_byte_count + 'd1;
                        end
                    end
                end
            end
            else begin
                // falling edge detection
                data_byte_count <= 'd0;
                if(tx_was_active) begin
                    tx_was_active <= 1'b0;
                    tx_dat <= HEADER_SIZE;
                    if(packet_tx_bytes > 'd2)
                        total_data_byte_count <= total_data_byte_count + packet_tx_bytes - 'd2;
                    packet_tx_bytes <= 'd0;
                end

                if(total_data_byte_count == TOTAL_DATA_COUNT) begin
                    bi_usb_state <= BI_USB_STATE_DONE;
                end
            end
        end
        BI_USB_STATE_DONE: begin 
            bi_usb_state <= BI_USB_STATE_IDLE;
        end
        endcase
    end
end

assign DATA_READ_ENABLE = initial_data_latch || ( (data_byte_count == NUM_DATA_BYTES - 1) && TXPOP && TXACT && CAPTURE_DONE);

assign TXDAT = tx_dat;
assign TXDAT_LEN = tx_len;
assign TXCORK = ~((bi_usb_state == BI_USB_STATE_SENDING_TRIG) || (bi_usb_state == BI_USB_STATE_SENDING_DATA));

endmodule
