
module serial_to_spiflash
(
    input           ARESET, // asynchronous positive reset
    input           CLK, // USB PHY clock, 60MHz

    // USB interface
    input [3:0]     ENPT_SEL, // endpoint selection, compare to the CDC data endpoint
    input           RXACT, // receiver active
    input           RXVAL, // receiver data valid
    input [7:0]     RXDAT, // receiver data
    input           TXACT, // transmitter active
    input           TXPOP, // transmitter consumed a byte
    output [7:0]    TXDAT, // transmitter data
    output [11:0]   TXDAT_LEN, // length of this packet to send
    output          TXCORK, // set to 1 when no data available (NAK)

    // SPI Flash interface (APB)
    output [7:0]    PADDR,
    output          PENABLE,
    input [31:0]    PRDATA,
    input           PREADY,
    output          PSEL,
    output [31:0]   PWDATA,
    output          PWRITE

);

`ifndef CDC_DATA_EP_NUM
`define CDC_DATA_EP_NUM     4
`endif

reg                 start_read;
reg                 start_send;
reg                 pwrite;
reg                 psel;
reg                 penable;
reg [31:0]          pwdata;
reg [ 7:0]          paddr;

reg [11:0]          txdat_len;
reg [ 7:0]          txdat;
reg                 txcork;

`define REG_TRANSFMT        8'h10
`define REG_TRANSCTRL       8'h20
`define REG_CMD             8'h24
`define REG_ADDR            8'h28
`define REG_DATA            8'h2C
`define REG_CTRL            8'h30
`define REG_STATUS          8'h34
`define REG_INTREN          8'h38
`define REG_INTSTATUS       8'h3C
`define REG_TIMING          8'h40
`define REG_CONFIG          8'h7C

// Read 16 bytes from SPI Flash process:
// Set transfer control register (0x20) to 0x62.00.00.0F
//      - CmdEn = 1
//      - AddrEn = 1
//      - TransMode = 0x2 (read only)
//      - RdTranCnt = 15 (total number (16) - 1)
// Set SPI control register (0x30) to 0x00.00.00.02
//      - RXFIFORST = 1
// Set SPI address register (0x28) to 0x00.00.00.00
// Set SPI command register (0x24) to 0x00.00.00.03
// Read SPI data register (0x2C)
//      - 4 bytes at a time
//      - read multiple times to receive all bytes

localparam  RDSTATE_IDLE =      3'b000,
            RDSTATE_TRANS =     3'b001,
            RDSTATE_CTRL =      3'b010,
            RDSTATE_ADDR =      3'b011,
            RDSTATE_CMD =       3'b100,
            RDSTATE_READ =      3'b101,
            RDSTATE_DONE =      3'b110;
reg [2:0] spi_read_state;
reg [1:0] spi_read_counter;

reg [31:0] spi_read_mem[3:0];
generate
  genvar idx;
  for(idx = 0; idx < 4; idx = idx+1) begin: register
    wire [31:0] tmp;
    assign tmp = spi_read_mem[idx];
  end
endgenerate

reg [1:0] init_done;

always @(posedge CLK or posedge ARESET) begin
    if(ARESET) begin
        spi_read_state <= RDSTATE_IDLE;
        spi_read_counter <= 2'd0;
        pwrite <= 1'b0;
        pwdata <= 32'h0;
        paddr <= 8'h0;
        psel <= 1'b0;
        penable <= 1'b0;
        init_done <= 1'b0;
    end
    else begin
        if(init_done != 2'b11) begin
            if(~init_done[0]) begin
                pwrite <= 1'b1;
                paddr <= `REG_TIMING;
                pwdata <= 32'h00_00_00_01;
                psel <= 1'b1;
                if(psel && ~penable)
                    penable <= 1'b1;
                if(PREADY && penable) begin
                    psel <= 1'b0;
                    penable <= 1'b0;
                    init_done[0] <= 1'b1;
                end
            end 
            else begin
                if(~init_done[1]) begin
                    pwrite <= 1'b1;
                    paddr <= `REG_TRANSFMT;
                    pwdata <= 32'h00_02_07_80;
                    psel <= 1'b1;
                    if(psel && ~penable)
                        penable <= 1'b1;
                    if(PREADY && penable) begin
                        psel <= 1'b0;
                        penable <= 1'b0;
                        init_done[1] <= 1'b1;
                    end
                end
            end
        end 
        else begin


            case (spi_read_state)
            RDSTATE_IDLE: begin
                // Trigger the read with a message from the USB serial port
                if(start_read)
                    spi_read_state <= RDSTATE_TRANS;
            end
            RDSTATE_TRANS: begin
                pwrite <= 1'b1;
                paddr <= `REG_TRANSCTRL;
                pwdata <= 32'h62_00_00_0F;
                psel <= 1'b1;
                if(psel && ~penable)
                    penable <= 1'b1;
                if(PREADY && penable) begin
                    spi_read_state <= RDSTATE_CTRL;
                    psel <= 1'b0;
                    penable <= 1'b0;
                end
            end
            RDSTATE_CTRL: begin
                pwrite <= 1'b1;
                paddr <= `REG_CTRL;
                pwdata <= 32'h00_00_00_02;
                psel <= 1'b1;
                if(psel && ~penable)
                    penable <= 1'b1;
                if(PREADY && penable) begin
                    spi_read_state <= RDSTATE_ADDR;
                    psel <= 1'b0;
                    penable <= 1'b0;
                end
            end
            RDSTATE_ADDR: begin
                pwrite <= 1'b1;
                paddr <= `REG_ADDR;
                pwdata <= 32'h00_00_00_00;
                psel <= 1'b1;
                if(psel && ~penable)
                    penable <= 1'b1;
                if(PREADY && penable) begin
                    spi_read_state <= RDSTATE_CMD;
                    psel <= 1'b0;
                    penable <= 1'b0;
                end
            end
            RDSTATE_CMD: begin
                pwrite <= 1'b1;
                paddr <= `REG_CMD;
                pwdata <= 32'h00_00_00_03;
                psel <= 1'b1;
                if(psel && ~penable)
                    penable <= 1'b1;
                if(PREADY && penable) begin
                    spi_read_state <= RDSTATE_READ;
                    psel <= 1'b0;
                    penable <= 1'b0;
                    spi_read_counter <= 2'd0;
                end
            end
            RDSTATE_READ: begin
                pwrite <= 1'b0;
                paddr <= `REG_DATA;
                psel <= 1'b1;
                if(psel && ~penable)
                    penable <= 1'b1;
                if(PREADY && penable) begin
                    psel <= 1'b0;
                    penable <= 1'b0;
                    if(spi_read_counter == 2'd3)
                        spi_read_state <= RDSTATE_DONE;
                    else
                        spi_read_counter <= spi_read_counter + 'd1;
                    spi_read_mem[spi_read_counter] <= PRDATA;
                end
            end
            RDSTATE_DONE: begin
                if(~start_read)
                    spi_read_state <= RDSTATE_IDLE;
            end
            default: ;
            endcase
        end
    end
end


reg [3:0] usb_byte_count;

always @(posedge CLK or posedge ARESET) begin
    if(ARESET) begin
        usb_byte_count <= 4'd0;
        txdat_len <= 12'd0;
        txdat <= 8'd0;
        txcork <= 1'b0;
        start_send <= 1'b0;
        start_read <= 1'b0;
    end
    else begin
        if(~start_read) begin
            if((ENPT_SEL == `CDC_DATA_EP_NUM) && RXACT && RXVAL) begin
                // new byte on serial port
                if(RXDAT == 8'h52) // 'R'
                    start_read <= 1'b1;
            end
        end
        else begin
            if(spi_read_state == RDSTATE_DONE) begin
                start_read <= 1'b0;
                start_send <= 1'b1;
                usb_byte_count <= 4'd0;
            end
        end

        if(start_send) begin
            txdat <= spi_read_mem[usb_byte_count[3:2]] >> (8*usb_byte_count[1:0]);
            if(~TXACT) begin
                // do not change these values while TXACT is true
                txdat_len <= 12'd16;
                txcork <= 1'b0;
            end

            if((ENPT_SEL == `CDC_DATA_EP_NUM) && TXACT && TXPOP) begin
                if(usb_byte_count == 4'd15)
                    start_send <= 1'b0;
                else
                    usb_byte_count <= usb_byte_count + 'd1;
            end
        end
        else begin
            if(~TXACT) begin
                txdat_len <= 12'd0;
                txcork <= 1'b1;
            end
        end
    end
end

assign TXDAT = txdat;
assign TXDAT_LEN = txdat_len;
assign TXCORK = txcork;

assign PADDR = paddr;
assign PENABLE = penable;
assign PSEL = psel;
assign PWDATA = pwdata;
assign PWRITE = pwrite;

endmodule
